@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,160 @@
1
+ // @ts-check
2
+ // Canvas links as navigation. The markdown, the diagrams and the chat all write links in the
3
+ // canvas scheme (#layer:, #file:, #hunk:, #line:), and the same string works as a URL fragment,
4
+ // so `…/review/278#line:path/to/file.ts:40` opens the page on that line and a click on a link
5
+ // leaves a URL the reader can share. The jump itself is `jumpTo` in anchors.js; this module
6
+ // decides when it runs.
7
+ import { cssEscape, drawCardOf, jumpTo, keyFromPath, nearestRow } from './anchors.js'
8
+ import { flash, scrollIntoViewSafe } from './dom.js'
9
+ import { fileAnchorId, layerAnchorId } from './keys.js'
10
+ import { parseLink } from './links.js'
11
+ import { markRailCurrent } from './scroll-spy.js'
12
+
13
+ /**
14
+ * The fragment with its percent escapes resolved, for a browser that hands back an encoded hash.
15
+ * A path holds `/`, `:` and `-`, which the parser reads raw, and a malformed escape is left as
16
+ * written rather than throwing.
17
+ * @param {string} hash
18
+ * @returns {string}
19
+ */
20
+ export function decodeHash(hash) {
21
+ try {
22
+ return decodeURIComponent(hash)
23
+ } catch {
24
+ return hash
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Follows one canvas link: scrolls to its target and highlights it. A line whose row is folded
30
+ * as noise or missing lands on the nearest row instead, marked `is-approx` the way the point and
31
+ * thread rows are. Returns false when the link does not parse or has no target on the page.
32
+ *
33
+ * The string is tried as written first, so a path that holds a literal `%` keeps it, and then
34
+ * with its escapes resolved, which is how the same link comes back out of the URL bar.
35
+ * @param {string} href
36
+ * @param {ParentNode} [root]
37
+ * @returns {boolean}
38
+ */
39
+ export function followLink(href, root = document) {
40
+ if (jumpToTarget(href, root)) {
41
+ return true
42
+ }
43
+ const decoded = decodeHash(href)
44
+ return decoded !== href && jumpToTarget(decoded, root)
45
+ }
46
+
47
+ /**
48
+ * @param {string} target
49
+ * @param {ParentNode} root
50
+ * @returns {boolean}
51
+ */
52
+ function jumpToTarget(target, root) {
53
+ const link = parseLink(target)
54
+ if (link === null) {
55
+ return false
56
+ }
57
+ if (link.kind === 'line') {
58
+ return jumpToLine(root, link)
59
+ }
60
+ if (!jumpTo(target, root)) {
61
+ return false
62
+ }
63
+ if (link.kind === 'layer') {
64
+ markRailCurrent(root, layerAnchorId(link.layerKey))
65
+ }
66
+ return true
67
+ }
68
+
69
+ /**
70
+ * The row of a line link, or the closest one when that line is folded away as noise or is not
71
+ * among the rows on screen.
72
+ * @param {ParentNode} root
73
+ * @param {{ path: string, start: number, side: 'new' | 'old' }} link
74
+ * @returns {boolean}
75
+ */
76
+ function jumpToLine(root, link) {
77
+ const key = keyFromPath(link.path)
78
+ drawCardOf(root, link.path)
79
+ const card = root.querySelector(`#${cssEscape(fileAnchorId(key))}`)
80
+ const near = nearestRow(card ?? root, key, link.side, link.start)
81
+ if (near === null) {
82
+ return false
83
+ }
84
+ const body = near.row.closest('.file')?.querySelector('.file-body')
85
+ if (body instanceof HTMLElement && body.hidden) {
86
+ body.hidden = false
87
+ }
88
+ if (near.approx) {
89
+ near.row.classList.add('is-approx')
90
+ }
91
+ scrollIntoViewSafe(near.row)
92
+ flash(near.row)
93
+ return true
94
+ }
95
+
96
+ /**
97
+ * A plain left click the page is free to take over. A modified click is the reader asking the
98
+ * browser for a new tab or window.
99
+ * @param {MouseEvent} event
100
+ */
101
+ function plainClick(event) {
102
+ return (
103
+ !event.defaultPrevented &&
104
+ (event.button === undefined || event.button === 0) &&
105
+ !event.metaKey &&
106
+ !event.ctrlKey &&
107
+ !event.shiftKey &&
108
+ !event.altKey
109
+ )
110
+ }
111
+
112
+ /**
113
+ * Wires canvas links for one screen: clicks on the anchors the renderers wrote, the fragment the
114
+ * page was opened with, and every later `hashchange`. `stop` removes all of it, so a re-render
115
+ * never leaves two sets of listeners behind.
116
+ * @param {HTMLElement | Document} root
117
+ * @param {{ view?: Window, jumpOnInit?: boolean }} [opts]
118
+ * @returns {{ stop: () => void, follow: (href: string) => boolean, followHash: () => boolean }}
119
+ */
120
+ export function initDeepLinks(root, opts = {}) {
121
+ const view = opts.view ?? window
122
+ const listeners = new AbortController()
123
+
124
+ const followHash = () => followLink(view.location.hash, root)
125
+
126
+ root.addEventListener(
127
+ 'click',
128
+ event => {
129
+ const mouse = /** @type {MouseEvent} */ (event)
130
+ const el = event.target instanceof Element ? event.target.closest('a[data-link], a[href^="#"]') : null
131
+ if (el === null || !plainClick(mouse)) {
132
+ return
133
+ }
134
+ const href = el.getAttribute('data-link') ?? el.getAttribute('href') ?? ''
135
+ if (parseLink(href) === null) {
136
+ return
137
+ }
138
+ event.preventDefault()
139
+ followLink(href, root)
140
+ // The URL stays shareable, and no second jump follows: replaceState fires no hashchange.
141
+ try {
142
+ view.history.replaceState(null, '', href)
143
+ } catch {
144
+ // A view without session history keeps the URL it had; the jump already happened.
145
+ }
146
+ },
147
+ { signal: listeners.signal }
148
+ )
149
+
150
+ view.addEventListener('hashchange', () => void followHash(), { signal: listeners.signal })
151
+ if (opts.jumpOnInit !== false) {
152
+ followHash()
153
+ }
154
+
155
+ return {
156
+ stop: () => listeners.abort(),
157
+ follow: href => followLink(href, root),
158
+ followHash,
159
+ }
160
+ }