semantic-code-review 0.24.2__py3-none-any.whl

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.
Files changed (100) hide show
  1. semantic_code_review/__init__.py +5 -0
  2. semantic_code_review/augment/__init__.py +3 -0
  3. semantic_code_review/augment/agents.py +95 -0
  4. semantic_code_review/augment/console.py +440 -0
  5. semantic_code_review/augment/extra_review.py +237 -0
  6. semantic_code_review/augment/fold_summary.py +447 -0
  7. semantic_code_review/augment/hunks.py +264 -0
  8. semantic_code_review/augment/mcp_server.py +156 -0
  9. semantic_code_review/augment/overview.py +233 -0
  10. semantic_code_review/augment/pass_.py +171 -0
  11. semantic_code_review/augment/pipeline.py +571 -0
  12. semantic_code_review/augment/progress.py +223 -0
  13. semantic_code_review/augment/prompts.py +93 -0
  14. semantic_code_review/augment/schemas.py +408 -0
  15. semantic_code_review/augment/tools.py +475 -0
  16. semantic_code_review/augment/trace_adapter.py +369 -0
  17. semantic_code_review/backends/__init__.py +95 -0
  18. semantic_code_review/backends/_cli_driver.py +571 -0
  19. semantic_code_review/backends/anthropic_sdk.py +56 -0
  20. semantic_code_review/backends/base.py +104 -0
  21. semantic_code_review/backends/claude_cli.py +421 -0
  22. semantic_code_review/backends/google_sdk.py +73 -0
  23. semantic_code_review/backends/openai_compat.py +42 -0
  24. semantic_code_review/cache/__init__.py +3 -0
  25. semantic_code_review/cache/store.py +87 -0
  26. semantic_code_review/cli/__init__.py +70 -0
  27. semantic_code_review/cli/_shared.py +157 -0
  28. semantic_code_review/cli/augment.py +74 -0
  29. semantic_code_review/cli/config_cmd.py +266 -0
  30. semantic_code_review/cli/fetch.py +33 -0
  31. semantic_code_review/cli/lint.py +27 -0
  32. semantic_code_review/cli/pr.py +98 -0
  33. semantic_code_review/cli/review.py +104 -0
  34. semantic_code_review/cli/runs_cmd.py +17 -0
  35. semantic_code_review/cli/show.py +19 -0
  36. semantic_code_review/cli/strip.py +20 -0
  37. semantic_code_review/config.py +563 -0
  38. semantic_code_review/config_template.py +154 -0
  39. semantic_code_review/fetch/__init__.py +61 -0
  40. semantic_code_review/fetch/anchor.py +244 -0
  41. semantic_code_review/fetch/github.py +229 -0
  42. semantic_code_review/fetch/github_comments.py +404 -0
  43. semantic_code_review/fetch/local.py +443 -0
  44. semantic_code_review/fetch/run_source.py +70 -0
  45. semantic_code_review/format/__init__.py +3 -0
  46. semantic_code_review/format/emit.py +163 -0
  47. semantic_code_review/format/lint.py +74 -0
  48. semantic_code_review/format/parse.py +574 -0
  49. semantic_code_review/format/sidecar.py +19 -0
  50. semantic_code_review/format/strip.py +20 -0
  51. semantic_code_review/git_ops.py +252 -0
  52. semantic_code_review/paths.py +61 -0
  53. semantic_code_review/review/__init__.py +3 -0
  54. semantic_code_review/review/comments.py +176 -0
  55. semantic_code_review/review/github.py +284 -0
  56. semantic_code_review/review/github_graphql.py +433 -0
  57. semantic_code_review/review/pr_flow.py +299 -0
  58. semantic_code_review/review/runner.py +407 -0
  59. semantic_code_review/review/server.py +1043 -0
  60. semantic_code_review/structural/__init__.py +41 -0
  61. semantic_code_review/structural/diff.py +102 -0
  62. semantic_code_review/structural/parse.py +330 -0
  63. semantic_code_review/structural/symbols.py +43 -0
  64. semantic_code_review/viewer/__init__.py +3 -0
  65. semantic_code_review/viewer/assets/annotations.ts +547 -0
  66. semantic_code_review/viewer/assets/boot.ts +273 -0
  67. semantic_code_review/viewer/assets/comment_store.ts +121 -0
  68. semantic_code_review/viewer/assets/comments.ts +624 -0
  69. semantic_code_review/viewer/assets/console.ts +426 -0
  70. semantic_code_review/viewer/assets/console_render.ts +213 -0
  71. semantic_code_review/viewer/assets/console_selection.ts +102 -0
  72. semantic_code_review/viewer/assets/data_store.ts +156 -0
  73. semantic_code_review/viewer/assets/file_rows.ts +49 -0
  74. semantic_code_review/viewer/assets/folds.ts +560 -0
  75. semantic_code_review/viewer/assets/index.html +62 -0
  76. semantic_code_review/viewer/assets/post_modal.ts +383 -0
  77. semantic_code_review/viewer/assets/progress.ts +138 -0
  78. semantic_code_review/viewer/assets/render.ts +937 -0
  79. semantic_code_review/viewer/assets/sidebar.ts +429 -0
  80. semantic_code_review/viewer/assets/sse.ts +78 -0
  81. semantic_code_review/viewer/assets/text_highlight.ts +215 -0
  82. semantic_code_review/viewer/assets/types.d.ts +388 -0
  83. semantic_code_review/viewer/assets/vendor/LICENSE +29 -0
  84. semantic_code_review/viewer/assets/vendor/VENDOR.md +55 -0
  85. semantic_code_review/viewer/assets/vendor/github-dark.min.css +10 -0
  86. semantic_code_review/viewer/assets/vendor/github.min.css +10 -0
  87. semantic_code_review/viewer/assets/vendor/highlight.min.js +1244 -0
  88. semantic_code_review/viewer/assets/vendor/mermaid.LICENSE +21 -0
  89. semantic_code_review/viewer/assets/vendor/mermaid.min.js +3587 -0
  90. semantic_code_review/viewer/assets/vendor/refresh.sh +65 -0
  91. semantic_code_review/viewer/assets/viewer.css +1202 -0
  92. semantic_code_review/viewer/assets/viewer.js +10588 -0
  93. semantic_code_review/viewer/build_json.py +546 -0
  94. semantic_code_review/viewer/hunk_layout.py +471 -0
  95. semantic_code_review-0.24.2.dist-info/METADATA +348 -0
  96. semantic_code_review-0.24.2.dist-info/RECORD +100 -0
  97. semantic_code_review-0.24.2.dist-info/WHEEL +5 -0
  98. semantic_code_review-0.24.2.dist-info/entry_points.txt +2 -0
  99. semantic_code_review-0.24.2.dist-info/licenses/LICENSE +21 -0
  100. semantic_code_review-0.24.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,102 @@
1
+ // Console selection resolution — "ask about *this*".
2
+ //
3
+ // Slice 4 (ADR 0002): turn a browser text selection into a structured
4
+ // hint the console can fold into a turn. We walk the selection's anchor
5
+ // up the rendered diff DOM, reusing the same `.cell-lineno` / `.hunk` /
6
+ // `.file` resolution the comment gutter uses (`comments.ts`), and
7
+ // classify the selection as code / comment / plain:
8
+ //
9
+ // - code — inside a diff row; resolves to (file, side, hunk_id,
10
+ // line_range) so the server can inline the enclosing hunk.
11
+ // - comment — inside a reviewer/LLM annotation; carries just the text.
12
+ // - plain — anything else (prose, the overview); carries just the text.
13
+ //
14
+ // Selections inside the console UI itself (the prompt, the drawer, the
15
+ // footer) resolve to null so caret moves in the textarea don't register
16
+ // as a pinned selection. This module is pure DOM → data; `console.ts`
17
+ // owns the chip, the live tracking, and the wire payload.
18
+
19
+ export interface ConsoleSelection {
20
+ selection_text: string;
21
+ selection_kind: "code" | "comment" | "plain";
22
+ file?: string;
23
+ side?: "old" | "new";
24
+ hunk_id?: string;
25
+ /** [start, end] line numbers on `side`, inclusive; collapsed to a
26
+ * single line when the selection stays within one row. */
27
+ line_range?: [number, number];
28
+ }
29
+
30
+ /** The element a (possibly text) node lives in, for `.closest()` walks. */
31
+ function elementOf(node: Node | null): Element | null {
32
+ if (!node) return null;
33
+ return node.nodeType === Node.ELEMENT_NODE
34
+ ? (node as Element)
35
+ : node.parentElement;
36
+ }
37
+
38
+ /** Resolve a node's enclosing diff row to its (side, line) — the same
39
+ * shape `comments.ts` reads off `row.children[0]`. Null when the node
40
+ * isn't in a numbered code row. */
41
+ function rowLine(node: Node | null): { side: "old" | "new"; line: number } | null {
42
+ const row = elementOf(node)?.closest(".row");
43
+ if (!row) return null;
44
+ const cell = row.children[0] as HTMLElement | undefined;
45
+ if (!cell || !cell.classList.contains("cell-lineno")) return null;
46
+ if (cell.classList.contains("empty")) return null;
47
+ const side: "old" | "new" =
48
+ cell.classList.contains("cell-lineno-old") ? "old" : "new";
49
+ const line = parseInt((cell.textContent || "").trim(), 10);
50
+ if (isNaN(line)) return null;
51
+ return { side, line };
52
+ }
53
+
54
+ /** Resolve a live selection to a console hint, or null when there's
55
+ * nothing usable (collapsed, empty, or inside the console UI). */
56
+ export function resolveSelection(sel: Selection | null): ConsoleSelection | null {
57
+ if (!sel || sel.isCollapsed) return null;
58
+ const text = sel.toString().trim();
59
+ if (!text) return null;
60
+
61
+ const anchorEl = elementOf(sel.anchorNode);
62
+ if (!anchorEl) return { selection_text: text, selection_kind: "plain" };
63
+
64
+ // Ignore the console's own surfaces — a caret move in the prompt or a
65
+ // drag across the transcript isn't a selection of the change.
66
+ if (anchorEl.closest(".console-drawer, .console-input, #status-bar")) {
67
+ return null;
68
+ }
69
+
70
+ // Comment / annotation text — reviewer threads and LLM annotations.
71
+ if (anchorEl.closest(".comment-thread, .comment-editor-body, .row-annotation")) {
72
+ return { selection_text: text, selection_kind: "comment" };
73
+ }
74
+
75
+ // Code — must sit inside a diff row of a hunk in a file.
76
+ const hunkEl = anchorEl.closest(".hunk");
77
+ const fileEl = anchorEl.closest(".file");
78
+ const anchor = rowLine(sel.anchorNode);
79
+ if (hunkEl && fileEl && anchor) {
80
+ const hunk_id = hunkEl.getAttribute("data-id") || undefined;
81
+ const pathEl = fileEl.querySelector(".file-path");
82
+ const file = pathEl ? (pathEl.textContent || "").trim() : "";
83
+ // The focus row may differ from the anchor row on a multi-line drag;
84
+ // keep the range on the anchor's side and span the two line numbers.
85
+ const focus = rowLine(sel.focusNode);
86
+ const lines = [anchor.line];
87
+ if (focus && focus.side === anchor.side) lines.push(focus.line);
88
+ const lo = Math.min(...lines);
89
+ const hi = Math.max(...lines);
90
+ const out: ConsoleSelection = {
91
+ selection_text: text,
92
+ selection_kind: "code",
93
+ side: anchor.side,
94
+ line_range: [lo, hi],
95
+ };
96
+ if (file) out.file = file;
97
+ if (hunk_id) out.hunk_id = hunk_id;
98
+ return out;
99
+ }
100
+
101
+ return { selection_text: text, selection_kind: "plain" };
102
+ }
@@ -0,0 +1,156 @@
1
+ // Typed mutators for the viewer's in-memory [[viewer-data]] tree.
2
+ //
3
+ // Before this module existed, boot.ts owned a few hundred lines of
4
+ // SSE-handler logic that walked DATA in place, mutated nested slots,
5
+ // and silently augmented HunkBlock / FoldRegion with private
6
+ // `_failed` / `_inflight` flags via local `... extends HunkBlock`
7
+ // interfaces. The renderer and folds.ts independently re-declared
8
+ // the same augmentations — three places, no shared contract.
9
+ //
10
+ // This module owns those mutations. boot.ts's SSE handlers shrink to
11
+ // "translate payload into a DataStore call, then ask the right module
12
+ // to repaint." The viewer-runtime fields (`_failed`, `_inflight`) are
13
+ // now part of HunkBlock / FoldRegion in types.d.ts; DataStore is
14
+ // their canonical writer.
15
+ //
16
+ // Functional API: every mutator takes the `ViewerData` reference as
17
+ // its first argument. boot.ts still holds the reference (it owns the
18
+ // `/data.json` fetch result); DataStore is stateless. That keeps the
19
+ // existing pattern where Comments.init / Sidebar.init / Render.init
20
+ // each receive the same reference and see mutations through it.
21
+
22
+ export interface FoldRegionAddress {
23
+ file_idx: number;
24
+ context: FoldContext;
25
+ right_start: number;
26
+ right_end: number;
27
+ left_start: number;
28
+ left_end: number;
29
+ }
30
+
31
+ export interface ResolvedFoldRegion {
32
+ file: FileBlock;
33
+ /** Index into `file.hunks` of the hunk whose `fold_regions` list
34
+ * carries the addressed region. Regions are addressed at the file
35
+ * level but persisted on individual hunks (today, the first hunk
36
+ * of the file — see CONTEXT.md `Fold region`). */
37
+ hostHunkIdx: number;
38
+ region: FoldRegion;
39
+ }
40
+
41
+ export type FoldSummaryResult = "applied" | "noop" | "inflight" | "not-found";
42
+
43
+ export interface OverviewApplyResult {
44
+ /** True iff `payload.groups` was non-empty; tells boot.ts whether
45
+ * to refresh the themes axis in addition to a full re-render. */
46
+ groupsChanged: boolean;
47
+ }
48
+
49
+ export const DataStore = {
50
+ // --- Overview / per-file metadata ----------------------------------
51
+
52
+ applyOverview(data: ViewerData, payload: SseOverviewEvent): OverviewApplyResult {
53
+ if (payload.pr) {
54
+ Object.assign(data.pr || (data.pr = {} as PRBlock), payload.pr);
55
+ }
56
+ if (Array.isArray(payload.files)) {
57
+ for (const fp of payload.files) {
58
+ const f = data.files && data.files[fp.file_idx];
59
+ if (!f) continue;
60
+ if (fp.summary !== undefined) f.summary = fp.summary;
61
+ if (fp.language) f.language = fp.language;
62
+ if (fp.symbols) f.symbols = fp.symbols;
63
+ if (fp.status) f.status = fp.status;
64
+ }
65
+ }
66
+ const groupsChanged = Array.isArray(payload.groups);
67
+ if (groupsChanged) {
68
+ data.groups = payload.groups!;
69
+ }
70
+ return { groupsChanged };
71
+ },
72
+
73
+ // --- Per-hunk replacement ------------------------------------------
74
+
75
+ /** Splice an augmented HunkBlock into the tree. Returns the file
76
+ * whose hunk was replaced (so the caller can hand it to
77
+ * Render.renderHunkReplace), or null if the address is invalid. */
78
+ replaceHunk(
79
+ data: ViewerData, file_idx: number, hunk_idx: number, block: HunkBlock,
80
+ ): FileBlock | null {
81
+ const file = data.files && data.files[file_idx];
82
+ if (!file || !file.hunks || !file.hunks[hunk_idx]) return null;
83
+ file.hunks[hunk_idx] = block;
84
+ return file;
85
+ },
86
+
87
+ /** Mark a hunk slot as failed: the renderer reads `_failed` to show
88
+ * "couldn't produce annotations" instead of the pending spinner. */
89
+ markHunkFailed(
90
+ data: ViewerData, file_idx: number, hunk_idx: number,
91
+ ): FileBlock | null {
92
+ const file = data.files && data.files[file_idx];
93
+ if (!file || !file.hunks || !file.hunks[hunk_idx]) return null;
94
+ const hunk = file.hunks[hunk_idx];
95
+ hunk.intent = "";
96
+ hunk._failed = true;
97
+ return file;
98
+ },
99
+
100
+ // --- Fold regions --------------------------------------------------
101
+
102
+ /** Look up the fold region at `addr`, walking every hunk in the
103
+ * addressed file. Returns null if no matching region exists. */
104
+ findFoldRegion(data: ViewerData, addr: FoldRegionAddress): ResolvedFoldRegion | null {
105
+ const f = data.files && data.files[addr.file_idx];
106
+ if (!f) return null;
107
+ for (let hi = 0; hi < (f.hunks || []).length; hi++) {
108
+ const h = f.hunks[hi];
109
+ for (const r of h.fold_regions || []) {
110
+ if (_foldKeyMatches(r, addr)) {
111
+ return { file: f, hostHunkIdx: hi, region: r };
112
+ }
113
+ }
114
+ }
115
+ return null;
116
+ },
117
+
118
+ /** Apply a server-side fold summary to the matching region.
119
+ *
120
+ * Outcomes:
121
+ * "applied" — summary written; caller should re-render.
122
+ * "noop" — same summary already present; nothing to do.
123
+ * "inflight" — region's own local POST is racing this; caller
124
+ * must NOT re-render (the in-flight handler owns
125
+ * the DOM update, and a re-render would pop the
126
+ * user's just-closed fold back open).
127
+ * "not-found" — the address doesn't match any region. */
128
+ applyFoldSummary(
129
+ data: ViewerData, addr: FoldRegionAddress, summary: string,
130
+ ): FoldSummaryResult {
131
+ const resolved = this.findFoldRegion(data, addr);
132
+ if (!resolved) return "not-found";
133
+ if (resolved.region.summary === summary) return "noop";
134
+ resolved.region.summary = summary;
135
+ if (resolved.region._inflight) return "inflight";
136
+ return "applied";
137
+ },
138
+
139
+ // --- Stream lifecycle ----------------------------------------------
140
+
141
+ finalisePending(data: ViewerData): void {
142
+ // Hunks the server never sent an event for (filtered, skipped,
143
+ // crashed mid-pass) now render the failure copy instead of the
144
+ // spinner on the next paint.
145
+ data.pending = false;
146
+ },
147
+ };
148
+
149
+
150
+ function _foldKeyMatches(r: FoldRegion, addr: FoldRegionAddress): boolean {
151
+ return (r.context || "right") === addr.context
152
+ && (r.right_start || 0) === addr.right_start
153
+ && (r.right_end || 0) === addr.right_end
154
+ && (r.left_start || 0) === addr.left_start
155
+ && (r.left_end || 0) === addr.left_end;
156
+ }
@@ -0,0 +1,49 @@
1
+ // Typed handle from the renderer to the fold-detector for the per-file
2
+ // row stream.
3
+ //
4
+ // Why this module exists. The fold detector (folds.ts) needs to walk
5
+ // every row in a file — across hunks, across expanded gap-context — in
6
+ // DOM order, with access to both the `RowBlock` records and the
7
+ // per-side DOM elements those rows materialised into. The renderer
8
+ // (render.ts) is the only place that has all three pieces at the
9
+ // moment of construction.
10
+ //
11
+ // The previous shape stashed three optional properties (`_scrRows`,
12
+ // `_scrRowElsOld`, `_scrRowElsNew`) directly on the `.diff` /
13
+ // `.gap-expansion` HTMLElements via three independently-declared
14
+ // HTMLElement augmentation interfaces. That worked, but the contract
15
+ // was triplicated, stringly-typed, and silent: a rename on the
16
+ // producing side broke the consuming side at runtime, not at compile
17
+ // time.
18
+ //
19
+ // This module owns the contract instead. `record` is called by the
20
+ // renderer for every `.diff` / `.gap-expansion` it builds; `get` is
21
+ // called by the fold detector for each candidate container as it walks
22
+ // `.file-body`. Storage is a `WeakMap` keyed by container reference,
23
+ // so entries vanish when a container is replaced (collapsed back to a
24
+ // chip, re-rendered after an SSE hunk replace, etc.).
25
+
26
+ export interface RowWithEls extends RowBlock {
27
+ oldEl: HTMLElement;
28
+ newEl: HTMLElement;
29
+ }
30
+
31
+ export interface FileRowsEntry {
32
+ rows: RowBlock[];
33
+ oldEls: HTMLElement[];
34
+ newEls: HTMLElement[];
35
+ }
36
+
37
+ const _storage = new WeakMap<HTMLElement, FileRowsEntry>();
38
+
39
+ export const FileRows = {
40
+ /** Record the row stream for a `.diff` or `.gap-expansion` container. */
41
+ record(container: HTMLElement, entry: FileRowsEntry): void {
42
+ _storage.set(container, entry);
43
+ },
44
+
45
+ /** Look up a container's recorded rows, or undefined if none was set. */
46
+ get(container: HTMLElement): FileRowsEntry | undefined {
47
+ return _storage.get(container);
48
+ },
49
+ };