@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.
- package/LICENSE +202 -0
- package/README.md +192 -0
- package/bin/pr-review.mjs +5 -0
- package/docs/reference.md +340 -0
- package/package.json +74 -0
- package/pr-review.config.example.yml +68 -0
- package/prompts/chat-seed.md +64 -0
- package/prompts/generation-format.md +255 -0
- package/prompts/generation-strict.md +34 -0
- package/prompts/generation-surfacing.md +67 -0
- package/prompts/layers-default.md +13 -0
- package/prompts/quality-standards.md +32 -0
- package/skills/pr-review-canvas/SKILL.md +177 -0
- package/src/acpx/acpx.ts +530 -0
- package/src/acpx/agents.ts +85 -0
- package/src/acpx/events.ts +216 -0
- package/src/acpx/ndjson.ts +69 -0
- package/src/acpx/preflight.ts +44 -0
- package/src/canvas/export.ts +95 -0
- package/src/canvas/import.ts +138 -0
- package/src/canvas/name.ts +55 -0
- package/src/canvas/zip.ts +123 -0
- package/src/chat/chat-manager.ts +389 -0
- package/src/chat/context.ts +160 -0
- package/src/chat/seed.ts +71 -0
- package/src/chat/threads.ts +114 -0
- package/src/cli.ts +199 -0
- package/src/commands.ts +424 -0
- package/src/config.ts +142 -0
- package/src/contract/api.ts +190 -0
- package/src/contract/canvas-manifest.ts +29 -0
- package/src/contract/chat.ts +76 -0
- package/src/contract/comments.ts +96 -0
- package/src/contract/discovery.ts +20 -0
- package/src/contract/generation-context.ts +77 -0
- package/src/contract/keys.ts +14 -0
- package/src/contract/links.ts +5 -0
- package/src/contract/mermaid-fences.ts +4 -0
- package/src/contract/review-artifact.ts +324 -0
- package/src/contract/settings.ts +144 -0
- package/src/contract/state.ts +46 -0
- package/src/contract/validation.ts +43 -0
- package/src/git/diff-collector.ts +151 -0
- package/src/git/git.ts +115 -0
- package/src/git/lang.ts +1 -0
- package/src/git/materialize.ts +79 -0
- package/src/git/patch-lines.ts +60 -0
- package/src/github/attachments.ts +288 -0
- package/src/github/capabilities.ts +112 -0
- package/src/github/comments.ts +132 -0
- package/src/github/gh.ts +196 -0
- package/src/github/post-comment.ts +104 -0
- package/src/github/post-review.ts +44 -0
- package/src/github/pr.ts +133 -0
- package/src/github/review-body.ts +72 -0
- package/src/github/threads.ts +63 -0
- package/src/paths.ts +10 -0
- package/src/project-config.ts +219 -0
- package/src/prompt-files.ts +26 -0
- package/src/review/diagram-nodes.ts +227 -0
- package/src/review/doctor.ts +139 -0
- package/src/review/glob.ts +33 -0
- package/src/review/install-skill.ts +107 -0
- package/src/review/normalize.ts +209 -0
- package/src/review/prepare.ts +165 -0
- package/src/review/prompt.ts +233 -0
- package/src/review/publish.ts +209 -0
- package/src/review/skill-command.ts +4 -0
- package/src/review/test-paths.ts +32 -0
- package/src/review/text-length.ts +15 -0
- package/src/review/trim-caps.ts +114 -0
- package/src/review/validate-folds.ts +110 -0
- package/src/review/validate.ts +520 -0
- package/src/server/app.ts +46 -0
- package/src/server/bundle.ts +266 -0
- package/src/server/capped-body.ts +62 -0
- package/src/server/context.ts +174 -0
- package/src/server/env.ts +7 -0
- package/src/server/errors.ts +65 -0
- package/src/server/html.ts +140 -0
- package/src/server/node-server.ts +42 -0
- package/src/server/routes/api.ts +256 -0
- package/src/server/routes/chat-routes.ts +221 -0
- package/src/server/routes/pages.ts +64 -0
- package/src/server/routes/review-routes.ts +245 -0
- package/src/server/routes/static.ts +114 -0
- package/src/server/security.ts +104 -0
- package/src/server/sse.ts +67 -0
- package/src/store/atomic-json.ts +68 -0
- package/src/store/canvas-store.ts +120 -0
- package/src/store/data-dir.ts +29 -0
- package/src/store/derived-store.ts +93 -0
- package/src/store/pr-store.ts +69 -0
- package/src/store/settings-store.ts +152 -0
- package/src/store/state-store.ts +121 -0
- package/static/js/anchors.js +141 -0
- package/static/js/api.js +542 -0
- package/static/js/app.js +418 -0
- package/static/js/ask.js +35 -0
- package/static/js/chat-context.js +137 -0
- package/static/js/chat-scroll.js +114 -0
- package/static/js/chat.js +843 -0
- package/static/js/code-folds.js +200 -0
- package/static/js/commands.js +110 -0
- package/static/js/comment-link.js +37 -0
- package/static/js/composer.js +241 -0
- package/static/js/contract-types.d.ts +59 -0
- package/static/js/deep-link.js +160 -0
- package/static/js/diagram.js +582 -0
- package/static/js/diff-decorations.js +204 -0
- package/static/js/diff-renderer.js +860 -0
- package/static/js/dom.js +145 -0
- package/static/js/download.js +52 -0
- package/static/js/empty-state.js +161 -0
- package/static/js/errors.js +135 -0
- package/static/js/fences.js +90 -0
- package/static/js/header.js +134 -0
- package/static/js/hunks.js +62 -0
- package/static/js/import-zone.js +95 -0
- package/static/js/interactions.js +952 -0
- package/static/js/keyboard.js +131 -0
- package/static/js/keys.js +97 -0
- package/static/js/lang.js +54 -0
- package/static/js/layers.js +596 -0
- package/static/js/links.js +150 -0
- package/static/js/markdown.js +232 -0
- package/static/js/mermaid-fences.js +55 -0
- package/static/js/nav.js +91 -0
- package/static/js/overview.js +85 -0
- package/static/js/points.js +247 -0
- package/static/js/progress.js +49 -0
- package/static/js/proposed-comment.js +133 -0
- package/static/js/quick-questions.js +216 -0
- package/static/js/regenerate.js +69 -0
- package/static/js/review-session.js +257 -0
- package/static/js/scroll-spy.js +66 -0
- package/static/js/selection.js +193 -0
- package/static/js/settings.js +206 -0
- package/static/js/signoff.js +171 -0
- package/static/js/skin.js +56 -0
- package/static/js/store.js +35 -0
- package/static/js/theme.js +56 -0
- package/static/js/threads.js +78 -0
- package/static/js/vendor.d.ts +15 -0
- package/static/styles/base.css +223 -0
- package/static/styles/chat-tools.css +130 -0
- package/static/styles/chat.css +140 -0
- package/static/styles/commands.css +156 -0
- package/static/styles/diff.css +258 -0
- package/static/styles/header.css +114 -0
- package/static/styles/layout.css +123 -0
- package/static/styles/panels.css +152 -0
- package/static/styles/responsive.css +80 -0
- package/static/styles/review-actions.css +124 -0
- package/static/styles/review.css +473 -0
- package/static/styles/skin-github.css +356 -0
- 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
|
+
}
|