ctxr-cli 0.24.0 → 0.26.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 (140) hide show
  1. package/README.md +59 -4
  2. package/dist/commands/land.d.ts +50 -0
  3. package/dist/commands/land.d.ts.map +1 -0
  4. package/dist/commands/land.js +414 -0
  5. package/dist/commands/land.js.map +1 -0
  6. package/dist/commands/publish-check.d.ts +12 -0
  7. package/dist/commands/publish-check.d.ts.map +1 -1
  8. package/dist/commands/publish-check.js +38 -7
  9. package/dist/commands/publish-check.js.map +1 -1
  10. package/dist/commands/publish-urls.d.ts +7 -0
  11. package/dist/commands/publish-urls.d.ts.map +1 -1
  12. package/dist/commands/publish-urls.js +13 -5
  13. package/dist/commands/publish-urls.js.map +1 -1
  14. package/dist/commands/review-list.d.ts +34 -0
  15. package/dist/commands/review-list.d.ts.map +1 -0
  16. package/dist/commands/review-list.js +49 -0
  17. package/dist/commands/review-list.js.map +1 -0
  18. package/dist/commands/serve.d.ts +3 -6
  19. package/dist/commands/serve.d.ts.map +1 -1
  20. package/dist/commands/serve.js +81 -48
  21. package/dist/commands/serve.js.map +1 -1
  22. package/dist/config/schema.d.ts +40 -0
  23. package/dist/config/schema.d.ts.map +1 -1
  24. package/dist/config/schema.js +39 -0
  25. package/dist/config/schema.js.map +1 -1
  26. package/dist/core/approval/decision.d.ts +73 -0
  27. package/dist/core/approval/decision.d.ts.map +1 -0
  28. package/dist/core/approval/decision.js +166 -0
  29. package/dist/core/approval/decision.js.map +1 -0
  30. package/dist/core/approval/file-diff.d.ts +32 -0
  31. package/dist/core/approval/file-diff.d.ts.map +1 -0
  32. package/dist/core/approval/file-diff.js +83 -0
  33. package/dist/core/approval/file-diff.js.map +1 -0
  34. package/dist/core/approval/identity.d.ts +34 -0
  35. package/dist/core/approval/identity.d.ts.map +1 -0
  36. package/dist/core/approval/identity.js +105 -0
  37. package/dist/core/approval/identity.js.map +1 -0
  38. package/dist/core/approval/landing-record.d.ts +17 -0
  39. package/dist/core/approval/landing-record.d.ts.map +1 -0
  40. package/dist/core/approval/landing-record.js +30 -0
  41. package/dist/core/approval/landing-record.js.map +1 -0
  42. package/dist/core/approval/resolver.d.ts +15 -0
  43. package/dist/core/approval/resolver.d.ts.map +1 -0
  44. package/dist/core/approval/resolver.js +41 -0
  45. package/dist/core/approval/resolver.js.map +1 -0
  46. package/dist/core/approval/sessions.d.ts +36 -0
  47. package/dist/core/approval/sessions.d.ts.map +1 -0
  48. package/dist/core/approval/sessions.js +76 -0
  49. package/dist/core/approval/sessions.js.map +1 -0
  50. package/dist/core/browse/approve.d.ts +48 -0
  51. package/dist/core/browse/approve.d.ts.map +1 -0
  52. package/dist/core/browse/approve.js +284 -0
  53. package/dist/core/browse/approve.js.map +1 -0
  54. package/dist/core/browse/memo.d.ts +7 -0
  55. package/dist/core/browse/memo.d.ts.map +1 -0
  56. package/dist/core/browse/memo.js +56 -0
  57. package/dist/core/browse/memo.js.map +1 -0
  58. package/dist/core/browse/review.d.ts +22 -0
  59. package/dist/core/browse/review.d.ts.map +1 -0
  60. package/dist/core/browse/review.js +58 -0
  61. package/dist/core/browse/review.js.map +1 -0
  62. package/dist/core/browse/serving-state.d.ts +45 -0
  63. package/dist/core/browse/serving-state.d.ts.map +1 -0
  64. package/dist/core/browse/serving-state.js +108 -0
  65. package/dist/core/browse/serving-state.js.map +1 -0
  66. package/dist/core/checks/organize-checks.js +2 -2
  67. package/dist/core/checks/organize-checks.js.map +1 -1
  68. package/dist/core/errors.d.ts +24 -0
  69. package/dist/core/errors.d.ts.map +1 -1
  70. package/dist/core/errors.js +55 -0
  71. package/dist/core/errors.js.map +1 -1
  72. package/dist/core/forge/credentials.d.ts +69 -0
  73. package/dist/core/forge/credentials.d.ts.map +1 -0
  74. package/dist/core/forge/credentials.js +197 -0
  75. package/dist/core/forge/credentials.js.map +1 -0
  76. package/dist/core/forge/github.d.ts +73 -0
  77. package/dist/core/forge/github.d.ts.map +1 -0
  78. package/dist/core/forge/github.js +101 -0
  79. package/dist/core/forge/github.js.map +1 -0
  80. package/dist/core/git/exec.d.ts +15 -0
  81. package/dist/core/git/exec.d.ts.map +1 -1
  82. package/dist/core/git/exec.js +48 -1
  83. package/dist/core/git/exec.js.map +1 -1
  84. package/dist/core/graph/diff-block.d.ts.map +1 -1
  85. package/dist/core/graph/diff-block.js +13 -4
  86. package/dist/core/graph/diff-block.js.map +1 -1
  87. package/dist/core/graph/diff-document.d.ts +3 -2
  88. package/dist/core/graph/diff-document.d.ts.map +1 -1
  89. package/dist/core/graph/diff-document.js +2 -4
  90. package/dist/core/graph/diff-document.js.map +1 -1
  91. package/dist/core/publish/lifespan.d.ts +5 -5
  92. package/dist/core/publish/lifespan.d.ts.map +1 -1
  93. package/dist/core/publish/lifespan.js +13 -8
  94. package/dist/core/publish/lifespan.js.map +1 -1
  95. package/dist/run.d.ts.map +1 -1
  96. package/dist/run.js +26 -0
  97. package/dist/run.js.map +1 -1
  98. package/dist/version.d.ts +1 -1
  99. package/dist/version.js +1 -1
  100. package/package.json +1 -1
  101. package/templates/approve/approve-button.md +1 -0
  102. package/templates/approve/author-heading.md +1 -0
  103. package/templates/approve/author-note.md +1 -0
  104. package/templates/approve/comment-label.md +1 -0
  105. package/templates/approve/comment-required.md +1 -0
  106. package/templates/approve/commits-heading.md +1 -0
  107. package/templates/approve/decision-heading.md +1 -0
  108. package/templates/approve/decision-note.md +1 -0
  109. package/templates/approve/diff-binary.md +1 -0
  110. package/templates/approve/diff-empty.md +1 -0
  111. package/templates/approve/diff-heading.md +1 -0
  112. package/templates/approve/facts-heading.md +1 -0
  113. package/templates/approve/forge-unreachable.md +1 -0
  114. package/templates/approve/github-app-manifest.json +15 -0
  115. package/templates/approve/head-missing.md +1 -0
  116. package/templates/approve/index-empty.md +1 -0
  117. package/templates/approve/index-heading.md +1 -0
  118. package/templates/approve/index-intro.md +1 -0
  119. package/templates/approve/landed.md +1 -0
  120. package/templates/approve/moved-head.md +1 -0
  121. package/templates/approve/no-pull.md +1 -0
  122. package/templates/approve/not-open.md +1 -0
  123. package/templates/approve/pages-heading.md +1 -0
  124. package/templates/approve/pages-note.md +1 -0
  125. package/templates/approve/record-failed.md +1 -0
  126. package/templates/approve/request-changes-button.md +1 -0
  127. package/templates/approve/state-line.md +1 -0
  128. package/templates/graph/knowledge-changes/archived-item.md +1 -0
  129. package/templates/graph/knowledge-changes/dangling-item.md +1 -0
  130. package/templates/graph/knowledge-changes/reason-ambiguous.md +1 -0
  131. package/templates/graph/knowledge-changes/reason-not_found.md +1 -0
  132. package/templates/graph/review/moved-head.md +1 -0
  133. package/templates/land/resolver.md +12 -0
  134. package/templates/serve/style.css +63 -0
  135. package/templates/skills/ctxr-land.md +37 -31
  136. package/templates/skills/ctxr-publish.md +25 -1
  137. package/templates/skills/ctxr-second-opinion/run.mjs +28 -17
  138. package/templates/skills/ctxr-second-opinion.md +19 -14
  139. package/templates/skills/ctxr-session-lifecycle.md +9 -0
  140. package/templates/skills/ctxr-submit.md +4 -2
@@ -0,0 +1,284 @@
1
+ import path from 'node:path';
2
+ import { pageUrlsFor } from '../../commands/publish-urls.js';
3
+ import { decisionBody, isDecider } from '../approval/decision.js';
4
+ import { readFileDiffs, renderFileDiffs } from '../approval/file-diff.js';
5
+ import { readLandingRecord } from '../approval/landing-record.js';
6
+ import { baseRef, forgeRead, reviewStateOf, sessionPulls, sessionWorktrees } from '../approval/sessions.js';
7
+ import { ForgeUnreachableError } from '../errors.js';
8
+ import { ForgeRequestError } from '../forge/github.js';
9
+ import { storePrefix } from '../graph/tree.js';
10
+ import { packagedTemplate } from '../templates.js';
11
+ import { escapeHtml, renderNoteBody } from './render.js';
12
+ import { configStamp, createMemo } from './memo.js';
13
+ import { renderReview } from './review.js';
14
+ import { resolveRange } from '../graph/diff.js';
15
+ /**
16
+ * change-approval spec: the approval index, one session's approval page, and
17
+ * the one route on this server that admits `POST`. Every path under the prefix
18
+ * is answered here, and only after the identity assertion verifies — nothing
19
+ * about the store or the forge is read before that.
20
+ */
21
+ export const APPROVE_ROUTE_PREFIX = '/approve/';
22
+ const DECISION_ROUTE = /^\/approve\/([^/]+)\/decision$/;
23
+ const PAGE_ROUTE = /^\/approve\/([^/]+)\/?$/;
24
+ const SHA = /^[0-9a-f]{40}$/;
25
+ const MAX_FORM_BYTES = 64 * 1024;
26
+ const MAX_COMMENT_CHARS = 20_000;
27
+ const text = (status, message) => ({ status, contentType: 'text/plain; charset=utf-8', body: `${message}\n` });
28
+ function copy(name) {
29
+ return packagedTemplate('approve', name);
30
+ }
31
+ export function isApprovePath(pathname) {
32
+ return pathname === '/approve' || pathname.startsWith(APPROVE_ROUTE_PREFIX);
33
+ }
34
+ export function isDecisionRoute(pathname) {
35
+ return DECISION_ROUTE.test(pathname);
36
+ }
37
+ function approveRoute(name) {
38
+ return `${APPROVE_ROUTE_PREFIX}${encodeURIComponent(name)}/`;
39
+ }
40
+ const STATE_LABEL = {
41
+ awaiting: 'awaiting a decision',
42
+ approved: 'approved, landing shortly',
43
+ 'changes-requested': 'changes requested',
44
+ 'needs-reapproval': 'needs a new decision',
45
+ blocked: 'blocked',
46
+ };
47
+ async function readForm(req) {
48
+ const chunks = [];
49
+ let size = 0;
50
+ for await (const chunk of req) {
51
+ size += chunk.length;
52
+ if (size > MAX_FORM_BYTES)
53
+ return null;
54
+ chunks.push(chunk);
55
+ }
56
+ return new URLSearchParams(Buffer.concat(chunks).toString('utf8'));
57
+ }
58
+ /**
59
+ * Page views reuse the open pull requests for a few seconds — the index and
60
+ * then a change are one visit, and listing them is a forge call. A decision
61
+ * never uses this: it drops the cache first, so the head it checks against,
62
+ * and any page it renders after, are read fresh.
63
+ */
64
+ const OPEN_PULLS_TTL_MS = 15_000;
65
+ const openPullsCache = new WeakMap();
66
+ function openPulls(server) {
67
+ const now = Date.now();
68
+ const cached = openPullsCache.get(server);
69
+ if (cached !== undefined && now - cached.at < OPEN_PULLS_TTL_MS)
70
+ return cached.pulls;
71
+ const pulls = server.forge.openPulls();
72
+ openPullsCache.set(server, { at: now, pulls });
73
+ pulls.catch(() => {
74
+ if (openPullsCache.get(server)?.pulls === pulls)
75
+ openPullsCache.delete(server);
76
+ });
77
+ return pulls;
78
+ }
79
+ const changeParts = createMemo(64);
80
+ async function locate(store, server, name) {
81
+ const worktree = (await sessionWorktrees(server.git, store.root, store.config)).find((w) => w.name === name);
82
+ if (worktree === undefined)
83
+ return null;
84
+ const open = await forgeRead(() => openPulls(server));
85
+ return { name, worktreePath: worktree.path, branch: worktree.branch, pull: open.find((p) => p.headRef === worktree.branch) ?? null };
86
+ }
87
+ async function indexPage(store, server, kit) {
88
+ const html = [`<h1>${escapeHtml(copy('index-heading'))}</h1>`];
89
+ try {
90
+ const pulls = await sessionPulls(server.git, store.root, store.config, server.forge, server.approval.recording_login, () => openPulls(server));
91
+ if (pulls.length === 0) {
92
+ html.push(`<p class="ctxr-empty">${escapeHtml(copy('index-empty'))}</p>`);
93
+ }
94
+ else {
95
+ html.push(`<p>${escapeHtml(copy('index-intro'))}</p>`);
96
+ html.push('<table class="ctxr-approve-index"><thead><tr><th>Change</th><th>Pull request</th><th>Commit</th><th>State</th></tr></thead><tbody>');
97
+ for (const p of pulls) {
98
+ html.push(`<tr><td><a href="${approveRoute(p.name)}">${escapeHtml(p.name)}</a></td><td>#${p.pull.number}</td>` +
99
+ `<td><code>${escapeHtml(p.pull.headSha.slice(0, 7))}</code></td><td>${escapeHtml(STATE_LABEL[p.review.state])}</td></tr>`);
100
+ }
101
+ html.push('</tbody></table>');
102
+ }
103
+ }
104
+ catch (err) {
105
+ if (!(err instanceof ForgeUnreachableError))
106
+ throw err;
107
+ html.push(`<p class="ctxr-approve-notice">${escapeHtml(copy('forge-unreachable'))}</p>`);
108
+ }
109
+ return { status: 200, contentType: 'text/html; charset=utf-8', body: await kit.page('approve', { html: html.join('\n'), hasDiagram: false }) };
110
+ }
111
+ async function renderChangePage(store, server, kit, name, notice = {}) {
112
+ const title = `approve — ${name}`;
113
+ const shell = async (status, parts, hasDiagram = false) => ({
114
+ status,
115
+ contentType: 'text/html; charset=utf-8',
116
+ body: await kit.page(title, { html: parts.join('\n'), hasDiagram }),
117
+ });
118
+ const html = [`<h1>${escapeHtml(name)}</h1>`];
119
+ if (notice.message !== undefined)
120
+ html.push(`<p class="ctxr-approve-notice">${escapeHtml(notice.message)}</p>`);
121
+ let located;
122
+ try {
123
+ located = await locate(store, server, name);
124
+ }
125
+ catch (err) {
126
+ if (!(err instanceof ForgeUnreachableError))
127
+ throw err;
128
+ return shell(notice.status ?? 200, [...html, `<p class="ctxr-approve-notice">${escapeHtml(copy('forge-unreachable'))}</p>`]);
129
+ }
130
+ if (located === null) {
131
+ const record = await readLandingRecord(store.root, name);
132
+ if (record === null)
133
+ return text(404, 'not found');
134
+ const landed = escapeHtml(copy('landed'))
135
+ .replace('__PR__', () => String(record.pull_request))
136
+ .replace('__COMMIT__', () => `<code>${escapeHtml(record.merged_commit ?? 'unknown')}</code>`);
137
+ return shell(200, [...html, `<p>${landed}</p>`]);
138
+ }
139
+ if (located.pull === null)
140
+ return shell(notice.status ?? 200, [...html, `<p class="ctxr-empty">${escapeHtml(copy('no-pull'))}</p>`]);
141
+ const pull = located.pull;
142
+ const head = pull.headSha;
143
+ if (notice.movedFrom !== undefined) {
144
+ const moved = escapeHtml(copy('moved-head'))
145
+ .replace('__RECORDED__', () => `<code>${escapeHtml(notice.movedFrom)}</code>`)
146
+ .replace('__CURRENT__', () => `<code>${escapeHtml(head)}</code>`);
147
+ html.push(`<p class="ctxr-review-moved">${moved}</p>`);
148
+ }
149
+ const present = await server.git.run(['cat-file', '-e', `${head}^{commit}`], { cwd: located.worktreePath, allowFailure: true });
150
+ if (present.exitCode !== 0) {
151
+ return shell(notice.status ?? 200, [...html, `<p class="ctxr-approve-notice">${escapeHtml(copy('head-missing')).replace('__HEAD__', () => `<code>${escapeHtml(head)}</code>`)}</p>`]);
152
+ }
153
+ const base = await baseRef(server.git, located.worktreePath, store.config.git.default_branch);
154
+ const prefix = await storePrefix(server.git, store.root);
155
+ const worktreeStoreRoot = path.join(located.worktreePath, prefix);
156
+ const mergeBase = (await resolveRange(server.git, located.worktreePath, base, head)).base;
157
+ const range = `${mergeBase}..${head}`;
158
+ // The decision state is read fresh every time; everything the commits fix is computed once per pair.
159
+ const [state, facts, parts] = await Promise.all([
160
+ forgeRead(() => reviewStateOf(server.git, located.worktreePath, base, server.forge, server.approval.recording_login, pull)),
161
+ renderReview(server.git, store, name, null, kit.linkFor, kit.resolveLink, head),
162
+ changeParts.get(`${configStamp(store.config)}\0${name}\0${prefix}\0${mergeBase}\0${head}`, async () => {
163
+ const [log, pages, files] = await Promise.all([
164
+ server.git.run(['log', '--reverse', '--no-color', '--format=%H%x1f%B%x1e', range], { cwd: located.worktreePath }),
165
+ pageUrlsFor({ root: worktreeStoreRoot, config: store.config }, [range]),
166
+ readFileDiffs(server.git, worktreeStoreRoot, mergeBase, head, store.config.derived.paths),
167
+ ]);
168
+ const commits = log.stdout
169
+ .split('\x1e')
170
+ .map((entry) => entry.trim())
171
+ .filter(Boolean)
172
+ .map((entry) => {
173
+ const [sha, message] = entry.split('\x1f');
174
+ return { sha, message: message.trim() };
175
+ });
176
+ return { commits, pages, files };
177
+ }),
178
+ ]);
179
+ let hasDiagram = false;
180
+ html.push(`<p>Pull request <a href="${escapeHtml(pull.url)}">#${pull.number}</a>, commit <code>${escapeHtml(head)}</code>. ${escapeHtml(copy('state-line').replace('__STATE__', STATE_LABEL[state.state]))}</p>`);
181
+ // The author's account: labelled as such, and kept apart from everything computed below.
182
+ const authorBody = renderNoteBody(pull.body, kit.resolveLink);
183
+ hasDiagram ||= authorBody.hasDiagram;
184
+ const commits = parts.commits.map((c) => `<li><code>${escapeHtml(c.sha.slice(0, 7))}</code><pre class="ctxr-commit-message">${escapeHtml(c.message)}</pre></li>`);
185
+ html.push(`<section class="ctxr-author-account"><h2>${escapeHtml(copy('author-heading'))}</h2>`, `<p class="ctxr-summary">${escapeHtml(copy('author-note'))}</p>`, `<h3>${escapeHtml(pull.title)}</h3>`, authorBody.html, `<h3>${escapeHtml(copy('commits-heading'))}</h3><ol class="ctxr-commits">${commits.join('\n')}</ol>`, '</section>');
186
+ hasDiagram ||= facts.hasDiagram;
187
+ html.push(`<section class="ctxr-derived"><h2>${escapeHtml(copy('facts-heading'))}</h2>`, facts.html, '</section>');
188
+ const pages = parts.pages;
189
+ if (pages.length > 0) {
190
+ const link = (address) => address === null ? '—' : `<a href="${escapeHtml(address.url ?? address.route)}">${escapeHtml(address.url ?? address.route)}</a>`;
191
+ const rows = pages.map((p) => `<tr><td>${escapeHtml(p.status)}</td><td>${escapeHtml(p.lifespan)}</td><td><code>${escapeHtml(p.path)}</code></td>` +
192
+ `<td>${p.status === 'removed' ? '—' : link(p.preview)}</td><td>${link(p.published)}</td></tr>`);
193
+ html.push(`<section class="ctxr-pages"><h2>${escapeHtml(copy('pages-heading'))}</h2><p class="ctxr-summary">${escapeHtml(copy('pages-note'))}</p>`, `<table><thead><tr><th>Status</th><th>Lifespan</th><th>Page</th><th>Preview</th><th>Published</th></tr></thead><tbody>${rows.join('')}</tbody></table></section>`);
194
+ }
195
+ const files = parts.files;
196
+ html.push(`<section class="ctxr-changed-files"><h2>${escapeHtml(copy('diff-heading'))}</h2>`, renderFileDiffs(files, { binary: copy('diff-binary'), noFiles: copy('diff-empty') }), '</section>');
197
+ const action = `${approveRoute(name)}decision`;
198
+ const note = escapeHtml(copy('decision-note')).replace('__HEAD__', () => `<code>${escapeHtml(head.slice(0, 7))}</code>`);
199
+ html.push(`<section class="ctxr-decision"><h2>${escapeHtml(copy('decision-heading'))}</h2><p>${note}</p>`, `<form method="post" action="${action}"><input type="hidden" name="decision" value="approve"><input type="hidden" name="head" value="${head}"><button type="submit">${escapeHtml(copy('approve-button'))}</button></form>`, `<form method="post" action="${action}"><input type="hidden" name="decision" value="request-changes"><input type="hidden" name="head" value="${head}">` +
200
+ `<label for="ctxr-comment">${escapeHtml(copy('comment-label'))}</label><textarea id="ctxr-comment" name="comment" rows="5" required></textarea>` +
201
+ `<button type="submit">${escapeHtml(copy('request-changes-button'))}</button></form>`, '</section>');
202
+ return shell(notice.status ?? 200, html, hasDiagram);
203
+ }
204
+ async function decide(store, server, makeKit, req, name, email) {
205
+ if (req.headers.origin !== server.origin)
206
+ return text(403, 'cross-site decision refused');
207
+ // A decision checks the head against the forge as it is now, never a cached list.
208
+ openPullsCache.delete(server);
209
+ const form = await readForm(req);
210
+ if (form === null)
211
+ return text(413, 'decision too large');
212
+ const decision = form.get('decision');
213
+ const head = form.get('head') ?? '';
214
+ const comment = (form.get('comment') ?? '').trim();
215
+ if ((decision !== 'approve' && decision !== 'request-changes') || !SHA.test(head))
216
+ return text(400, 'malformed decision');
217
+ if (comment.length > MAX_COMMENT_CHARS)
218
+ return text(413, 'comment too long');
219
+ let located;
220
+ try {
221
+ located = await locate(store, server, name);
222
+ }
223
+ catch (err) {
224
+ if (!(err instanceof ForgeUnreachableError))
225
+ throw err;
226
+ return renderChangePage(store, server, await makeKit(), name, { status: 502, message: copy('record-failed') });
227
+ }
228
+ if (located === null)
229
+ return text(404, 'not found');
230
+ if (located.pull === null || located.pull.state !== 'open')
231
+ return renderChangePage(store, server, await makeKit(), name, { status: 409, message: copy('not-open') });
232
+ if (located.pull.headSha !== head)
233
+ return renderChangePage(store, server, await makeKit(), name, { status: 409, movedFrom: head });
234
+ if (decision === 'request-changes' && comment === '')
235
+ return renderChangePage(store, server, await makeKit(), name, { status: 400, message: copy('comment-required') });
236
+ const kind = decision;
237
+ try {
238
+ await server.forge.createReview(located.pull.number, {
239
+ event: kind === 'approve' ? 'APPROVE' : 'REQUEST_CHANGES',
240
+ commitId: head,
241
+ body: decisionBody({ decision: kind, head, by: email, at: server.now().toISOString(), carriedFrom: null, comment: kind === 'approve' ? null : comment }),
242
+ });
243
+ }
244
+ catch (err) {
245
+ if (!(err instanceof ForgeRequestError))
246
+ throw err;
247
+ return renderChangePage(store, server, await makeKit(), name, { status: 502, message: copy('record-failed') });
248
+ }
249
+ // Post/redirect/get: the page the decider lands on reads the recorded state back from the forge.
250
+ return { status: 303, contentType: 'text/plain; charset=utf-8', body: 'recorded\n', headers: { Location: approveRoute(name) } };
251
+ }
252
+ /**
253
+ * Answers every request under the approval prefix. `server` is null when the
254
+ * store declares no change approval, and then every such path is `404` —
255
+ * unless `unavailable` says the block is declared but its surface could not be
256
+ * built after startup (serve-follows-config-changes D4), and then every such
257
+ * path is `503`, naming why. `makeKit` builds the browsing surface's shell only
258
+ * after the requester is known.
259
+ */
260
+ export async function handleApprove(store, server, req, method, pathname, makeKit, unavailable = null) {
261
+ if (server === null && unavailable !== null)
262
+ return text(503, `the approval page is unavailable: ${unavailable}`);
263
+ if (server === null)
264
+ return text(404, 'not found');
265
+ const verified = await server.verifier.verify(req.headers[server.approval.identity.header.toLowerCase()]);
266
+ if (!verified.ok)
267
+ return text(401, 'a verified identity assertion is required');
268
+ if (!isDecider(verified.email, server.approval.deciders))
269
+ return text(403, 'this identity may not decide on changes here');
270
+ const decisionMatch = DECISION_ROUTE.exec(pathname);
271
+ if (method === 'POST') {
272
+ if (decisionMatch === null)
273
+ return text(405, 'method not allowed');
274
+ // The shell reads the whole store, and an accepted decision only redirects, so it is built on the error paths alone.
275
+ return decide(store, server, makeKit, req, decisionMatch[1], verified.email);
276
+ }
277
+ if (pathname === '/approve' || pathname === APPROVE_ROUTE_PREFIX)
278
+ return indexPage(store, server, await makeKit());
279
+ const pageMatch = PAGE_ROUTE.exec(pathname);
280
+ if (pageMatch === null)
281
+ return text(404, 'not found');
282
+ return renderChangePage(store, server, await makeKit(), pageMatch[1]);
283
+ }
284
+ //# sourceMappingURL=approve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.js","sourceRoot":"","sources":["../../../src/core/browse/approve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function configStamp(config: object): number;
2
+ export interface Memo<V> {
3
+ get(key: string, compute: () => Promise<V>): Promise<V>;
4
+ readonly size: number;
5
+ }
6
+ export declare function createMemo<V>(max: number): Memo<V>;
7
+ //# sourceMappingURL=memo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../../../src/core/browse/memo.ts"],"names":[],"mappings":"AAqBA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED,MAAM,WAAW,IAAI,CAAC,CAAC;IACrB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAuBlD"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * approve-from-the-review-page (performance): a small least-recently-used
3
+ * memo for work whose result is fixed by its key — a knowledge delta, a line
4
+ * diff or a page list for one pair of commits never changes, so the second
5
+ * view of a change costs nothing. Holds the promise, so concurrent requests
6
+ * for the same key share one computation; a rejected computation is dropped,
7
+ * so a transient failure is not remembered.
8
+ *
9
+ * Process memory only, and bounded. `ctxr serve` is the one long-running
10
+ * process; every other command computes once and exits.
11
+ */
12
+ /**
13
+ * serve-follows-config-changes: a short stamp for one loaded configuration
14
+ * object. What a memo caches is fixed by its commits *and* the configuration
15
+ * it was computed with (taxonomy, publish path, derived paths), and the server
16
+ * now swaps configuration while it runs, so every key carries this stamp. Each
17
+ * reload yields a new object, hence a new stamp, so nothing computed under the
18
+ * old configuration is served under the new one.
19
+ */
20
+ const stamps = new WeakMap();
21
+ let nextStamp = 1;
22
+ export function configStamp(config) {
23
+ let stamp = stamps.get(config);
24
+ if (stamp === undefined) {
25
+ stamp = nextStamp++;
26
+ stamps.set(config, stamp);
27
+ }
28
+ return stamp;
29
+ }
30
+ export function createMemo(max) {
31
+ const entries = new Map();
32
+ return {
33
+ get(key, compute) {
34
+ const hit = entries.get(key);
35
+ if (hit !== undefined) {
36
+ // Map iteration order is insertion order: re-inserting marks the entry most recently used.
37
+ entries.delete(key);
38
+ entries.set(key, hit);
39
+ return hit;
40
+ }
41
+ const pending = compute();
42
+ entries.set(key, pending);
43
+ pending.catch(() => {
44
+ if (entries.get(key) === pending)
45
+ entries.delete(key);
46
+ });
47
+ while (entries.size > max)
48
+ entries.delete(entries.keys().next().value);
49
+ return pending;
50
+ },
51
+ get size() {
52
+ return entries.size;
53
+ },
54
+ };
55
+ }
56
+ //# sourceMappingURL=memo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memo.js","sourceRoot":"","sources":["../../../src/core/browse/memo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH;;;;;;;GAOG;AACH,MAAM,MAAM,GAAG,IAAI,OAAO,EAAkB,CAAC;AAC7C,IAAI,SAAS,GAAG,CAAC,CAAC;AAClB,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,KAAK,GAAG,SAAS,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAOD,MAAM,UAAU,UAAU,CAAI,GAAW;IACvC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC9C,OAAO;QACL,GAAG,CAAC,GAAG,EAAE,OAAO;YACd,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,2FAA2F;gBAC3F,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACpB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBACtB,OAAO,GAAG,CAAC;YACb,CAAC;YACD,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC1B,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;gBACjB,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;oBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACxD,CAAC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,IAAI,GAAG,GAAG;gBAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAC;YACxE,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,IAAI;YACN,OAAO,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { GitRunner } from '../git/exec.js';
2
+ import type { Store } from '../store.js';
3
+ import type { LinkResolver } from './link-resolver.js';
4
+ import { type RenderedBody } from './render.js';
5
+ /**
6
+ * context-browsing spec (review-the-knowledge-diff D5, D6): one session
7
+ * worktree's knowledge changes, derived from the delta `ctxr graph diff`
8
+ * computes for that worktree's `HEAD` against the default branch — `origin/`
9
+ * plus it when that ref exists, else the local branch, and never a fetch.
10
+ * Nothing an agent wrote about the change is read: no commit message, no pull
11
+ * request body, no file the change adds for the purpose.
12
+ *
13
+ * `until` names the head to compare (the approval page passes the pull
14
+ * request's head); the worktree's `HEAD` otherwise.
15
+ *
16
+ * Both sides come from git's object store, so the worktree's uncommitted work
17
+ * is neither shown nor touched. A path links to the note route only when this
18
+ * server's note route answers for it, so a worktree-only note stays text and
19
+ * the view widens nothing the note route serves.
20
+ */
21
+ export declare function renderReview(git: GitRunner, store: Store, worktree: string, recordedHead: string | null, linkFor: (notePath: string) => string | null, resolveLink: LinkResolver, until?: string): Promise<RenderedBody>;
22
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../../src/core/browse/review.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAA8B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAU5E;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EAC5C,WAAW,EAAE,YAAY,EACzB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC,CA2BvB"}
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import { renderDiffParts } from '../graph/diff-document.js';
3
+ import { computeGraphDelta, resolveRange } from '../graph/diff.js';
4
+ import { storePrefix } from '../graph/tree.js';
5
+ import { packagedTemplate } from '../templates.js';
6
+ import { configStamp, createMemo } from './memo.js';
7
+ import { escapeHtml, renderNoteBody } from './render.js';
8
+ /**
9
+ * The delta for one (store prefix, base, head) — commits are immutable, and the
10
+ * server's configuration is fixed for its lifetime, so the result never goes
11
+ * stale. Reading every note on both sides is the slowest thing a review or
12
+ * approval page does.
13
+ */
14
+ const deltas = createMemo(64);
15
+ /**
16
+ * context-browsing spec (review-the-knowledge-diff D5, D6): one session
17
+ * worktree's knowledge changes, derived from the delta `ctxr graph diff`
18
+ * computes for that worktree's `HEAD` against the default branch — `origin/`
19
+ * plus it when that ref exists, else the local branch, and never a fetch.
20
+ * Nothing an agent wrote about the change is read: no commit message, no pull
21
+ * request body, no file the change adds for the purpose.
22
+ *
23
+ * `until` names the head to compare (the approval page passes the pull
24
+ * request's head); the worktree's `HEAD` otherwise.
25
+ *
26
+ * Both sides come from git's object store, so the worktree's uncommitted work
27
+ * is neither shown nor touched. A path links to the note route only when this
28
+ * server's note route answers for it, so a worktree-only note stays text and
29
+ * the view widens nothing the note route serves.
30
+ */
31
+ export async function renderReview(git, store, worktree, recordedHead, linkFor, resolveLink, until) {
32
+ const prefix = await storePrefix(git, store.root);
33
+ const worktreeStoreRoot = path.join(store.root, store.config.session.worktrees_path, worktree, prefix);
34
+ const defaultBranch = store.config.git.default_branch;
35
+ const remote = await git.run(['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${defaultBranch}`], {
36
+ cwd: worktreeStoreRoot,
37
+ allowFailure: true,
38
+ });
39
+ const since = remote.exitCode === 0 ? `origin/${defaultBranch}` : defaultBranch;
40
+ const range = await resolveRange(git, worktreeStoreRoot, since, until);
41
+ const delta = await deltas.get(`${configStamp(store.config)}\0${prefix}\0${range.base}\0${range.head}`, () => computeGraphDelta(git, worktreeStoreRoot, range, store.config));
42
+ const html = [];
43
+ if (recordedHead !== null && recordedHead !== delta.head) {
44
+ const notice = escapeHtml(packagedTemplate('graph/review', 'moved-head'))
45
+ .replace('__RECORDED__', () => `<code>${escapeHtml(recordedHead)}</code>`)
46
+ .replace('__CURRENT__', () => `<code>${escapeHtml(delta.head)}</code>`);
47
+ html.push(`<p class="ctxr-review-moved">${notice}</p>`);
48
+ }
49
+ // render-diagrams-in-notes: the delta's diagram draws like a note's, so the page carries the renderer when any part has one.
50
+ let hasDiagram = false;
51
+ for (const part of renderDiffParts(delta, { linkFor, diagram: true })) {
52
+ const rendered = renderNoteBody(part.text, resolveLink);
53
+ hasDiagram ||= rendered.hasDiagram;
54
+ html.push(part.kind === 'markdown' ? rendered.html : `<details><summary>${escapeHtml(part.summary)}</summary>\n${rendered.html}</details>\n`);
55
+ }
56
+ return { html: html.join('\n'), hasDiagram };
57
+ }
58
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../../src/core/browse/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { StoreConfig } from '../../config/schema.js';
2
+ import type { Store } from '../store.js';
3
+ import type { ApprovalServer } from './approve.js';
4
+ /**
5
+ * serve-follows-config-changes: what `ctxr serve` answers a request with — the
6
+ * store as its configuration last loaded, and the approval surface built from
7
+ * it. A request takes one state at its start, so it never mixes two
8
+ * configurations (design.md D2).
9
+ */
10
+ export interface ServingState {
11
+ store: Store;
12
+ approval: ApprovalServer | null;
13
+ /** Set when change_approval is declared and the surface could not be built after startup (design.md D4). */
14
+ approvalError: string | null;
15
+ }
16
+ export interface ServingStateHolder {
17
+ current(): Promise<ServingState>;
18
+ }
19
+ export interface FileIdentity {
20
+ mtimeMs: number;
21
+ size: number;
22
+ dev: number;
23
+ ino: number;
24
+ }
25
+ export interface ServingStateOptions {
26
+ initial: Store;
27
+ initialApproval: ApprovalServer | null;
28
+ load(root: string): Promise<StoreConfig>;
29
+ /** Builds the approval surface for a store, or null when it declares none; throws when it cannot be built. */
30
+ buildApproval(store: Store): Promise<ApprovalServer | null>;
31
+ log(line: string): void;
32
+ now?: () => number;
33
+ identify?: (file: string) => Promise<FileIdentity>;
34
+ retryMs?: number;
35
+ }
36
+ /**
37
+ * context-browsing spec ("The server follows changes to the store's
38
+ * configuration"), design.md D1: one `stat` per request, compared on
39
+ * modification time, size and file identity, because the file is replaced by
40
+ * a rename (a watcher on the old inode goes silent). A changed file is read and
41
+ * validated again; one that does not load leaves the last good configuration in
42
+ * place and is reported once (D5).
43
+ */
44
+ export declare function createServingState(opts: ServingStateOptions): Promise<ServingStateHolder>;
45
+ //# sourceMappingURL=serving-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serving-state.d.ts","sourceRoot":"","sources":["../../../src/core/browse/serving-state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,4GAA4G;IAC5G,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;IACf,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACzC,8GAA8G;IAC9G,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC5D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA0BD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA2E/F"}