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.
- package/README.md +59 -4
- package/dist/commands/land.d.ts +50 -0
- package/dist/commands/land.d.ts.map +1 -0
- package/dist/commands/land.js +414 -0
- package/dist/commands/land.js.map +1 -0
- package/dist/commands/publish-check.d.ts +12 -0
- package/dist/commands/publish-check.d.ts.map +1 -1
- package/dist/commands/publish-check.js +38 -7
- package/dist/commands/publish-check.js.map +1 -1
- package/dist/commands/publish-urls.d.ts +7 -0
- package/dist/commands/publish-urls.d.ts.map +1 -1
- package/dist/commands/publish-urls.js +13 -5
- package/dist/commands/publish-urls.js.map +1 -1
- package/dist/commands/review-list.d.ts +34 -0
- package/dist/commands/review-list.d.ts.map +1 -0
- package/dist/commands/review-list.js +49 -0
- package/dist/commands/review-list.js.map +1 -0
- package/dist/commands/serve.d.ts +3 -6
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +81 -48
- package/dist/commands/serve.js.map +1 -1
- package/dist/config/schema.d.ts +40 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +39 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/approval/decision.d.ts +73 -0
- package/dist/core/approval/decision.d.ts.map +1 -0
- package/dist/core/approval/decision.js +166 -0
- package/dist/core/approval/decision.js.map +1 -0
- package/dist/core/approval/file-diff.d.ts +32 -0
- package/dist/core/approval/file-diff.d.ts.map +1 -0
- package/dist/core/approval/file-diff.js +83 -0
- package/dist/core/approval/file-diff.js.map +1 -0
- package/dist/core/approval/identity.d.ts +34 -0
- package/dist/core/approval/identity.d.ts.map +1 -0
- package/dist/core/approval/identity.js +105 -0
- package/dist/core/approval/identity.js.map +1 -0
- package/dist/core/approval/landing-record.d.ts +17 -0
- package/dist/core/approval/landing-record.d.ts.map +1 -0
- package/dist/core/approval/landing-record.js +30 -0
- package/dist/core/approval/landing-record.js.map +1 -0
- package/dist/core/approval/resolver.d.ts +15 -0
- package/dist/core/approval/resolver.d.ts.map +1 -0
- package/dist/core/approval/resolver.js +41 -0
- package/dist/core/approval/resolver.js.map +1 -0
- package/dist/core/approval/sessions.d.ts +36 -0
- package/dist/core/approval/sessions.d.ts.map +1 -0
- package/dist/core/approval/sessions.js +76 -0
- package/dist/core/approval/sessions.js.map +1 -0
- package/dist/core/browse/approve.d.ts +48 -0
- package/dist/core/browse/approve.d.ts.map +1 -0
- package/dist/core/browse/approve.js +284 -0
- package/dist/core/browse/approve.js.map +1 -0
- package/dist/core/browse/memo.d.ts +7 -0
- package/dist/core/browse/memo.d.ts.map +1 -0
- package/dist/core/browse/memo.js +56 -0
- package/dist/core/browse/memo.js.map +1 -0
- package/dist/core/browse/review.d.ts +22 -0
- package/dist/core/browse/review.d.ts.map +1 -0
- package/dist/core/browse/review.js +58 -0
- package/dist/core/browse/review.js.map +1 -0
- package/dist/core/browse/serving-state.d.ts +45 -0
- package/dist/core/browse/serving-state.d.ts.map +1 -0
- package/dist/core/browse/serving-state.js +108 -0
- package/dist/core/browse/serving-state.js.map +1 -0
- package/dist/core/checks/organize-checks.js +2 -2
- package/dist/core/checks/organize-checks.js.map +1 -1
- package/dist/core/errors.d.ts +24 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +55 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/forge/credentials.d.ts +69 -0
- package/dist/core/forge/credentials.d.ts.map +1 -0
- package/dist/core/forge/credentials.js +197 -0
- package/dist/core/forge/credentials.js.map +1 -0
- package/dist/core/forge/github.d.ts +73 -0
- package/dist/core/forge/github.d.ts.map +1 -0
- package/dist/core/forge/github.js +101 -0
- package/dist/core/forge/github.js.map +1 -0
- package/dist/core/git/exec.d.ts +15 -0
- package/dist/core/git/exec.d.ts.map +1 -1
- package/dist/core/git/exec.js +48 -1
- package/dist/core/git/exec.js.map +1 -1
- package/dist/core/graph/diff-block.d.ts.map +1 -1
- package/dist/core/graph/diff-block.js +13 -4
- package/dist/core/graph/diff-block.js.map +1 -1
- package/dist/core/graph/diff-document.d.ts +3 -2
- package/dist/core/graph/diff-document.d.ts.map +1 -1
- package/dist/core/graph/diff-document.js +2 -4
- package/dist/core/graph/diff-document.js.map +1 -1
- package/dist/core/publish/lifespan.d.ts +5 -5
- package/dist/core/publish/lifespan.d.ts.map +1 -1
- package/dist/core/publish/lifespan.js +13 -8
- package/dist/core/publish/lifespan.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +26 -0
- package/dist/run.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/templates/approve/approve-button.md +1 -0
- package/templates/approve/author-heading.md +1 -0
- package/templates/approve/author-note.md +1 -0
- package/templates/approve/comment-label.md +1 -0
- package/templates/approve/comment-required.md +1 -0
- package/templates/approve/commits-heading.md +1 -0
- package/templates/approve/decision-heading.md +1 -0
- package/templates/approve/decision-note.md +1 -0
- package/templates/approve/diff-binary.md +1 -0
- package/templates/approve/diff-empty.md +1 -0
- package/templates/approve/diff-heading.md +1 -0
- package/templates/approve/facts-heading.md +1 -0
- package/templates/approve/forge-unreachable.md +1 -0
- package/templates/approve/github-app-manifest.json +15 -0
- package/templates/approve/head-missing.md +1 -0
- package/templates/approve/index-empty.md +1 -0
- package/templates/approve/index-heading.md +1 -0
- package/templates/approve/index-intro.md +1 -0
- package/templates/approve/landed.md +1 -0
- package/templates/approve/moved-head.md +1 -0
- package/templates/approve/no-pull.md +1 -0
- package/templates/approve/not-open.md +1 -0
- package/templates/approve/pages-heading.md +1 -0
- package/templates/approve/pages-note.md +1 -0
- package/templates/approve/record-failed.md +1 -0
- package/templates/approve/request-changes-button.md +1 -0
- package/templates/approve/state-line.md +1 -0
- package/templates/graph/knowledge-changes/archived-item.md +1 -0
- package/templates/graph/knowledge-changes/dangling-item.md +1 -0
- package/templates/graph/knowledge-changes/reason-ambiguous.md +1 -0
- package/templates/graph/knowledge-changes/reason-not_found.md +1 -0
- package/templates/graph/review/moved-head.md +1 -0
- package/templates/land/resolver.md +12 -0
- package/templates/serve/style.css +63 -0
- package/templates/skills/ctxr-land.md +37 -31
- package/templates/skills/ctxr-publish.md +25 -1
- package/templates/skills/ctxr-second-opinion/run.mjs +28 -17
- package/templates/skills/ctxr-second-opinion.md +19 -14
- package/templates/skills/ctxr-session-lifecycle.md +9 -0
- 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"}
|