@vintasoftware/pr-review-canvas 0.1.0

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 (157) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +192 -0
  3. package/bin/pr-review.mjs +5 -0
  4. package/docs/reference.md +340 -0
  5. package/package.json +74 -0
  6. package/pr-review.config.example.yml +68 -0
  7. package/prompts/chat-seed.md +64 -0
  8. package/prompts/generation-format.md +255 -0
  9. package/prompts/generation-strict.md +34 -0
  10. package/prompts/generation-surfacing.md +67 -0
  11. package/prompts/layers-default.md +13 -0
  12. package/prompts/quality-standards.md +32 -0
  13. package/skills/pr-review-canvas/SKILL.md +177 -0
  14. package/src/acpx/acpx.ts +530 -0
  15. package/src/acpx/agents.ts +85 -0
  16. package/src/acpx/events.ts +216 -0
  17. package/src/acpx/ndjson.ts +69 -0
  18. package/src/acpx/preflight.ts +44 -0
  19. package/src/canvas/export.ts +95 -0
  20. package/src/canvas/import.ts +138 -0
  21. package/src/canvas/name.ts +55 -0
  22. package/src/canvas/zip.ts +123 -0
  23. package/src/chat/chat-manager.ts +389 -0
  24. package/src/chat/context.ts +160 -0
  25. package/src/chat/seed.ts +71 -0
  26. package/src/chat/threads.ts +114 -0
  27. package/src/cli.ts +199 -0
  28. package/src/commands.ts +424 -0
  29. package/src/config.ts +142 -0
  30. package/src/contract/api.ts +190 -0
  31. package/src/contract/canvas-manifest.ts +29 -0
  32. package/src/contract/chat.ts +76 -0
  33. package/src/contract/comments.ts +96 -0
  34. package/src/contract/discovery.ts +20 -0
  35. package/src/contract/generation-context.ts +77 -0
  36. package/src/contract/keys.ts +14 -0
  37. package/src/contract/links.ts +5 -0
  38. package/src/contract/mermaid-fences.ts +4 -0
  39. package/src/contract/review-artifact.ts +324 -0
  40. package/src/contract/settings.ts +144 -0
  41. package/src/contract/state.ts +46 -0
  42. package/src/contract/validation.ts +43 -0
  43. package/src/git/diff-collector.ts +151 -0
  44. package/src/git/git.ts +115 -0
  45. package/src/git/lang.ts +1 -0
  46. package/src/git/materialize.ts +79 -0
  47. package/src/git/patch-lines.ts +60 -0
  48. package/src/github/attachments.ts +288 -0
  49. package/src/github/capabilities.ts +112 -0
  50. package/src/github/comments.ts +132 -0
  51. package/src/github/gh.ts +196 -0
  52. package/src/github/post-comment.ts +104 -0
  53. package/src/github/post-review.ts +44 -0
  54. package/src/github/pr.ts +133 -0
  55. package/src/github/review-body.ts +72 -0
  56. package/src/github/threads.ts +63 -0
  57. package/src/paths.ts +10 -0
  58. package/src/project-config.ts +219 -0
  59. package/src/prompt-files.ts +26 -0
  60. package/src/review/diagram-nodes.ts +227 -0
  61. package/src/review/doctor.ts +139 -0
  62. package/src/review/glob.ts +33 -0
  63. package/src/review/install-skill.ts +107 -0
  64. package/src/review/normalize.ts +209 -0
  65. package/src/review/prepare.ts +165 -0
  66. package/src/review/prompt.ts +233 -0
  67. package/src/review/publish.ts +209 -0
  68. package/src/review/skill-command.ts +4 -0
  69. package/src/review/test-paths.ts +32 -0
  70. package/src/review/text-length.ts +15 -0
  71. package/src/review/trim-caps.ts +114 -0
  72. package/src/review/validate-folds.ts +110 -0
  73. package/src/review/validate.ts +520 -0
  74. package/src/server/app.ts +46 -0
  75. package/src/server/bundle.ts +266 -0
  76. package/src/server/capped-body.ts +62 -0
  77. package/src/server/context.ts +174 -0
  78. package/src/server/env.ts +7 -0
  79. package/src/server/errors.ts +65 -0
  80. package/src/server/html.ts +140 -0
  81. package/src/server/node-server.ts +42 -0
  82. package/src/server/routes/api.ts +256 -0
  83. package/src/server/routes/chat-routes.ts +221 -0
  84. package/src/server/routes/pages.ts +64 -0
  85. package/src/server/routes/review-routes.ts +245 -0
  86. package/src/server/routes/static.ts +114 -0
  87. package/src/server/security.ts +104 -0
  88. package/src/server/sse.ts +67 -0
  89. package/src/store/atomic-json.ts +68 -0
  90. package/src/store/canvas-store.ts +120 -0
  91. package/src/store/data-dir.ts +29 -0
  92. package/src/store/derived-store.ts +93 -0
  93. package/src/store/pr-store.ts +69 -0
  94. package/src/store/settings-store.ts +152 -0
  95. package/src/store/state-store.ts +121 -0
  96. package/static/js/anchors.js +141 -0
  97. package/static/js/api.js +542 -0
  98. package/static/js/app.js +418 -0
  99. package/static/js/ask.js +35 -0
  100. package/static/js/chat-context.js +137 -0
  101. package/static/js/chat-scroll.js +114 -0
  102. package/static/js/chat.js +843 -0
  103. package/static/js/code-folds.js +200 -0
  104. package/static/js/commands.js +110 -0
  105. package/static/js/comment-link.js +37 -0
  106. package/static/js/composer.js +241 -0
  107. package/static/js/contract-types.d.ts +59 -0
  108. package/static/js/deep-link.js +160 -0
  109. package/static/js/diagram.js +582 -0
  110. package/static/js/diff-decorations.js +204 -0
  111. package/static/js/diff-renderer.js +860 -0
  112. package/static/js/dom.js +145 -0
  113. package/static/js/download.js +52 -0
  114. package/static/js/empty-state.js +161 -0
  115. package/static/js/errors.js +135 -0
  116. package/static/js/fences.js +90 -0
  117. package/static/js/header.js +134 -0
  118. package/static/js/hunks.js +62 -0
  119. package/static/js/import-zone.js +95 -0
  120. package/static/js/interactions.js +952 -0
  121. package/static/js/keyboard.js +131 -0
  122. package/static/js/keys.js +97 -0
  123. package/static/js/lang.js +54 -0
  124. package/static/js/layers.js +596 -0
  125. package/static/js/links.js +150 -0
  126. package/static/js/markdown.js +232 -0
  127. package/static/js/mermaid-fences.js +55 -0
  128. package/static/js/nav.js +91 -0
  129. package/static/js/overview.js +85 -0
  130. package/static/js/points.js +247 -0
  131. package/static/js/progress.js +49 -0
  132. package/static/js/proposed-comment.js +133 -0
  133. package/static/js/quick-questions.js +216 -0
  134. package/static/js/regenerate.js +69 -0
  135. package/static/js/review-session.js +257 -0
  136. package/static/js/scroll-spy.js +66 -0
  137. package/static/js/selection.js +193 -0
  138. package/static/js/settings.js +206 -0
  139. package/static/js/signoff.js +171 -0
  140. package/static/js/skin.js +56 -0
  141. package/static/js/store.js +35 -0
  142. package/static/js/theme.js +56 -0
  143. package/static/js/threads.js +78 -0
  144. package/static/js/vendor.d.ts +15 -0
  145. package/static/styles/base.css +223 -0
  146. package/static/styles/chat-tools.css +130 -0
  147. package/static/styles/chat.css +140 -0
  148. package/static/styles/commands.css +156 -0
  149. package/static/styles/diff.css +258 -0
  150. package/static/styles/header.css +114 -0
  151. package/static/styles/layout.css +123 -0
  152. package/static/styles/panels.css +152 -0
  153. package/static/styles/responsive.css +80 -0
  154. package/static/styles/review-actions.css +124 -0
  155. package/static/styles/review.css +473 -0
  156. package/static/styles/skin-github.css +356 -0
  157. package/static/styles.css +14 -0
@@ -0,0 +1,204 @@
1
+ // @ts-check
2
+ // Rows added on top of a rendered diff: annotation bands, inline attention points, GitHub
3
+ // threads. applyDecorations is idempotent: it removes what it added before re-adding.
4
+ /** @typedef {import('./contract-types.js').Annotation} Annotation */
5
+ /** @typedef {import('./contract-types.js').Point} Point */
6
+ /** @typedef {import('./contract-types.js').ReviewComment} ReviewComment */
7
+ /** @typedef {import('./threads.js').Thread} Thread */
8
+ import { findRow, nearestRow } from './anchors.js'
9
+ import { viewCommentHtml } from './comment-link.js'
10
+ import { esc, fragment, avatarHtml, timeAgo } from './dom.js'
11
+ import { renderMarkdown } from './markdown.js'
12
+ import { pointRowHtml } from './points.js'
13
+
14
+ const DECORATION = 'data-decoration'
15
+
16
+ /**
17
+ * Rows from `start` to `end` on `side` get the annotation band edge; the note sits under the last one.
18
+ * @param {HTMLElement} card the file card (or any ancestor of the tables)
19
+ * @param {string} key
20
+ * @param {Annotation} a
21
+ * @returns {boolean} false when no row of the range is on the page
22
+ */
23
+ export function insertNoteRow(card, key, a) {
24
+ /** @type {HTMLTableRowElement | null} */
25
+ let last = null
26
+ for (let line = a.startLine; line <= a.endLine; line++) {
27
+ const row = findRow(card, key, a.side, line)
28
+ if (row) {
29
+ row.classList.add('ann')
30
+ last = row
31
+ }
32
+ }
33
+ let approx = false
34
+ if (last === null) {
35
+ const near = nearestRow(card, key, a.side, a.endLine)
36
+ if (near === null) {
37
+ return false
38
+ }
39
+ last = near.row
40
+ approx = true
41
+ }
42
+ const range = a.startLine === a.endLine ? `line ${a.startLine}` : `lines ${a.startLine}–${a.endLine}`
43
+ const html =
44
+ `<tr class="annot ann${approx ? ' is-approx' : ''}" ${DECORATION}="note"><td class="ln" colspan="3"></td><td class="code x">` +
45
+ `<span class="lbl">Annotation · ${esc(range)}${a.side === 'old' ? ' (old)' : ''}</span>` +
46
+ `<div class="prose">${renderMarkdown(a.text)}</div></td></tr>`
47
+ last.insertAdjacentElement('afterend', firstRow(html))
48
+ return true
49
+ }
50
+
51
+ /**
52
+ * @param {HTMLElement} card
53
+ * @param {string} key
54
+ * @param {Point} p
55
+ * @param {{ paths: ReadonlySet<string>, state?: import('./contract-types.js').PrState, posted?: ReadonlyMap<string, string> }} ctx
56
+ * @returns {boolean}
57
+ */
58
+ export function insertPointRow(card, key, p, ctx) {
59
+ const side = p.side ?? 'new'
60
+ const line = p.endLine ?? p.line
61
+ const near = nearestRow(card, key, side, line)
62
+ if (near === null) {
63
+ return false
64
+ }
65
+ const row = firstRow(pointRowHtml(p, ctx))
66
+ row.setAttribute(DECORATION, 'point')
67
+ if (near.approx) {
68
+ row.classList.add('is-approx')
69
+ }
70
+ near.row.insertAdjacentElement('afterend', row)
71
+ return true
72
+ }
73
+
74
+ /**
75
+ * @param {ReviewComment} c
76
+ * @param {Date} now
77
+ */
78
+ export function commentHtml(c, now) {
79
+ return (
80
+ `<div class="cmt">${avatarHtml(c)}` +
81
+ `<span class="who"><b>${esc(c.author)}</b> <span class="muted">${esc(timeAgo(c.createdAt, now))}</span> ${viewCommentHtml(c.url)}</span>` +
82
+ `<div class="prose">${renderMarkdown(c.body, { github: true })}</div></div>`
83
+ )
84
+ }
85
+
86
+ /**
87
+ * A thread row carries both views: the comments, and the one muted line a resolved or hidden
88
+ * thread collapses to. Showing one and hiding the other is then a local toggle with no data.
89
+ * @param {Thread} t
90
+ * @param {{ now: Date, hidden?: boolean }} opts
91
+ * @returns {string}
92
+ */
93
+ export function threadRowHtml(t, opts) {
94
+ const rootId = t.root.id
95
+ const hidden = opts.hidden === true
96
+ const collapsed = t.resolved || hidden
97
+ const label = t.resolved ? '1 resolved thread' : '1 hidden thread'
98
+ const comments = [t.root, ...t.replies].map(c => commentHtml(c, opts.now)).join('')
99
+ // A resolved thread folds away on the page only; an open one is hidden in the local state.
100
+ const hide = t.resolved
101
+ ? `<button class="cmd" type="button" data-act="thread-collapse" data-thread="${rootId}">collapse</button>`
102
+ : `<button class="cmd" type="button" data-act="thread-hide" data-thread="${rootId}">hide</button>`
103
+ return (
104
+ `<tr class="thread${t.resolved ? ' resolved' : ''}${hidden ? ' hidden-thread' : ''}" data-thread="${rootId}">` +
105
+ '<td class="ln" colspan="3"></td><td class="code x">' +
106
+ `<div class="thread-full"${collapsed ? ' hidden' : ''}>${comments}` +
107
+ `<span class="tbtns"><button class="cmd" type="button" data-act="thread-reply" data-thread="${rootId}" data-needs-post>reply</button>${hide}</span></div>` +
108
+ `<div class="thread-collapsed"${collapsed ? '' : ' hidden'}>${label} · ` +
109
+ `<button class="cmd" type="button" data-act="thread-show" data-thread="${rootId}">show</button></div>` +
110
+ '</td></tr>'
111
+ )
112
+ }
113
+
114
+ /**
115
+ * Swaps the two views of a thread row.
116
+ * @param {Element} row
117
+ * @param {boolean} collapsed
118
+ */
119
+ export function setThreadCollapsed(row, collapsed) {
120
+ row.querySelector('.thread-full')?.toggleAttribute('hidden', collapsed)
121
+ row.querySelector('.thread-collapsed')?.toggleAttribute('hidden', !collapsed)
122
+ row.classList.toggle('hidden-thread', collapsed && !row.classList.contains('resolved'))
123
+ }
124
+
125
+ /**
126
+ * @param {HTMLElement} card
127
+ * @param {string} key
128
+ * @param {Thread} t
129
+ * @param {{ now: Date, hidden?: boolean }} opts
130
+ * @returns {boolean}
131
+ */
132
+ export function insertThreadRow(card, key, t, opts) {
133
+ if (t.line === null) {
134
+ return false
135
+ }
136
+ const near = nearestRow(card, key, t.side, t.line)
137
+ if (near === null) {
138
+ return false
139
+ }
140
+ const row = firstRow(threadRowHtml(t, opts))
141
+ row.setAttribute(DECORATION, 'thread')
142
+ if (near.approx) {
143
+ row.classList.add('is-approx')
144
+ }
145
+ near.row.insertAdjacentElement('afterend', row)
146
+ return true
147
+ }
148
+
149
+ /**
150
+ * @param {string} html
151
+ * @returns {HTMLTableRowElement}
152
+ */
153
+ function firstRow(html) {
154
+ const tbody = document.createElement('tbody')
155
+ tbody.append(fragment(html))
156
+ const row = tbody.firstElementChild
157
+ if (!(row instanceof HTMLTableRowElement)) {
158
+ throw new Error('decoration html must start with <tr>')
159
+ }
160
+ return row
161
+ }
162
+
163
+ /**
164
+ * Removes every decoration row and annotation band, then adds them back from the data.
165
+ * Order under one line: annotation, then attention points, then threads (each insert goes
166
+ * right after the anchor row, so they are added in reverse).
167
+ * @param {HTMLElement} card
168
+ * @param {string} key
169
+ * @param {{ annotations: ReadonlyArray<Annotation>, points: ReadonlyArray<Point>, threads: ReadonlyArray<Thread>, paths: ReadonlySet<string>, now: Date, state?: import('./contract-types.js').PrState, posted?: ReadonlyMap<string, string>, hiddenThreads?: ReadonlySet<number> }} data
170
+ * @returns {{ placed: number, missed: number }}
171
+ */
172
+ export function applyDecorations(card, key, data) {
173
+ for (const row of Array.from(card.querySelectorAll(`tr[${DECORATION}]`))) {
174
+ row.remove()
175
+ }
176
+ for (const row of Array.from(card.querySelectorAll('tr.ann'))) {
177
+ row.classList.remove('ann')
178
+ }
179
+ let placed = 0
180
+ let missed = 0
181
+ const count = /** @param {boolean} ok */ ok => {
182
+ if (ok) {
183
+ placed++
184
+ } else {
185
+ missed++
186
+ }
187
+ }
188
+ for (const t of [...data.threads].reverse()) {
189
+ count(insertThreadRow(card, key, t, { now: data.now, hidden: data.hiddenThreads?.has(t.root.id) ?? false }))
190
+ }
191
+ for (const p of [...data.points].reverse()) {
192
+ count(
193
+ insertPointRow(card, key, p, {
194
+ paths: data.paths,
195
+ ...(data.state === undefined ? {} : { state: data.state }),
196
+ ...(data.posted === undefined ? {} : { posted: data.posted }),
197
+ })
198
+ )
199
+ }
200
+ for (const a of [...data.annotations].reverse()) {
201
+ count(insertNoteRow(card, key, a))
202
+ }
203
+ return { placed, missed }
204
+ }