comprehende 0.5.1 → 0.5.3
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 +1 -1
- package/dist/api/agent-md.js +204 -0
- package/dist/api/live.js +16 -2
- package/dist/api/paths.js +30 -0
- package/dist/git/repo.js +42 -0
- package/dist/server/http.js +1 -0
- package/dist/ui/assets/{index-CNz9KqcH.js → index-BGFe-Iyk.js} +61 -61
- package/dist/ui/assets/index-BQTydqt9.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/skills/comprehende/SKILL.md +4 -4
- package/dist/ui/assets/index-DJMUAjCi.css +0 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ This tool is for preventing this cognitive surrender while trying to maintain mo
|
|
|
42
42
|
|
|
43
43
|
`comprehende serve` and `comprehende export` share one UI and one git payload. Serve resolves refs to commit SHAs when it starts, then computes those payloads from the objects on each request. Export writes the same JSON (and image bytes) next to the UI so any static file server can host the review.
|
|
44
44
|
|
|
45
|
-
`pnpm dev` and `pnpm exec` run with this package as cwd, so they only make sense when _this_ repo is the one under review. To review a different project from a checkout, `cd` into it and run `npx comprehende@0.5.
|
|
45
|
+
`pnpm dev` and `pnpm exec` run with this package as cwd, so they only make sense when _this_ repo is the one under review. To review a different project from a checkout, `cd` into it and run `npx comprehende@0.5.3` (or `node /path/to/comprehende/dist/cli/main.js` after `pnpm build`).
|
|
46
46
|
|
|
47
47
|
## Release
|
|
48
48
|
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
export const AGENT_MD_MEDIA_TYPE = "text/markdown; charset=utf-8";
|
|
2
|
+
export function formatHunkRef(ref) {
|
|
3
|
+
const rename = ref.oldPath !== undefined ? `${ref.oldPath} -> ` : "";
|
|
4
|
+
return `${rename}${ref.path} @@ -${ref.oldStart},${ref.oldLines} +${ref.newStart},${ref.newLines} @@`;
|
|
5
|
+
}
|
|
6
|
+
export function isImageSlot(ref) {
|
|
7
|
+
return ref.oldStart === 0 && ref.oldLines === 0 && ref.newStart === 0 && ref.newLines === 0;
|
|
8
|
+
}
|
|
9
|
+
export function agentClipboardPrompt(url) {
|
|
10
|
+
return `Answer the following questions by using ${url}`;
|
|
11
|
+
}
|
|
12
|
+
function groupAgentMdHref(id) {
|
|
13
|
+
return `groups/${encodeURIComponent(id)}.md`;
|
|
14
|
+
}
|
|
15
|
+
export function agentMd(review, resource) {
|
|
16
|
+
if (resource.target === "overview") {
|
|
17
|
+
return overviewAgentMd(review);
|
|
18
|
+
}
|
|
19
|
+
return groupAgentMd(review, resource.group);
|
|
20
|
+
}
|
|
21
|
+
function overviewAgentMd(review) {
|
|
22
|
+
return joinBlocks([
|
|
23
|
+
"Answer questions about this git change.",
|
|
24
|
+
overviewSteps(review),
|
|
25
|
+
pinBlock(review, { commits: true }),
|
|
26
|
+
ticketsBlock(review),
|
|
27
|
+
coverageBlock(review),
|
|
28
|
+
review.document.why !== undefined ? joinBlocks(["The why:", review.document.why]) : null,
|
|
29
|
+
joinBlocks([`The what (${sizeLabel(review.document.size)}):`, review.document.summary]),
|
|
30
|
+
reviewConcernsBlock(review),
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
function groupAgentMd(review, id) {
|
|
34
|
+
const listed = review.groups.find((group) => group.id === id);
|
|
35
|
+
const documentGroup = review.document.groups.find((group) => group.id === id);
|
|
36
|
+
if (listed === undefined || documentGroup === undefined) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const index = review.groups.findIndex((group) => group.id === id) + 1;
|
|
40
|
+
const total = review.groups.length;
|
|
41
|
+
const hunks = documentGroup.hunkRefs;
|
|
42
|
+
const heading = `Review concern ${padIndex(index)} of ${padIndex(total)}: ${listed.title} (\`${listed.id}\`)`;
|
|
43
|
+
return joinBlocks([
|
|
44
|
+
"Answer questions about this review concern.",
|
|
45
|
+
groupSteps(review),
|
|
46
|
+
pinBlock(review, { commits: false }),
|
|
47
|
+
heading,
|
|
48
|
+
listed.part !== undefined ? `Part: ${listed.part}` : null,
|
|
49
|
+
"The why:",
|
|
50
|
+
listed.why,
|
|
51
|
+
"The what:",
|
|
52
|
+
listed.summary,
|
|
53
|
+
lookForBlock(listed.lookFor),
|
|
54
|
+
dependsOnBlock(review, listed.dependsOn),
|
|
55
|
+
hunkList("Hunk refs for this concern:", hunks),
|
|
56
|
+
imageNote(hunks),
|
|
57
|
+
listed.staleCount > 0
|
|
58
|
+
? `Stale hunk refs in this concern: ${listed.staleCount}. Live git wins. The pointer is flagged, not replaced.`
|
|
59
|
+
: null,
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
function pinBlock(review, options) {
|
|
63
|
+
const { baseSha, headSha, baseRef, headRef } = review.resolved;
|
|
64
|
+
const repo = review.repo.origin !== null
|
|
65
|
+
? `Repository: ${review.repo.name}\nOrigin: ${review.repo.origin}`
|
|
66
|
+
: `Repository: ${review.repo.name}`;
|
|
67
|
+
const commits = options.commits && review.commits.length > 0
|
|
68
|
+
? ["Commits:", ...review.commits.map((commit) => `- ${commit.shortSha} ${commit.subject}`)].join("\n")
|
|
69
|
+
: null;
|
|
70
|
+
return joinBlocks([
|
|
71
|
+
"## Pin",
|
|
72
|
+
repo,
|
|
73
|
+
`base (merge-base) ${baseSha}`,
|
|
74
|
+
`head ${headSha}`,
|
|
75
|
+
`Named refs at pin: ${baseRef} ... ${headRef}`,
|
|
76
|
+
"Read the diff:",
|
|
77
|
+
`git diff --find-renames ${baseSha} ${headSha}`,
|
|
78
|
+
commits,
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
function ticketsBlock(review) {
|
|
82
|
+
const tickets = review.document.tickets ?? [];
|
|
83
|
+
if (tickets.length === 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const lines = tickets.map((ticket) => {
|
|
87
|
+
const title = ticket.title !== undefined ? ` ${ticket.title}` : "";
|
|
88
|
+
const url = ticket.url !== undefined ? `\n ${ticket.url}` : "";
|
|
89
|
+
return `- ${ticket.id}${title}${url}`;
|
|
90
|
+
});
|
|
91
|
+
return ["Tickets:", ...lines].join("\n");
|
|
92
|
+
}
|
|
93
|
+
function coverageBlock(review) {
|
|
94
|
+
const lines = [];
|
|
95
|
+
if (review.coverage.unassignedCount > 0) {
|
|
96
|
+
lines.push(`Unassigned live hunks: ${review.coverage.unassignedCount}. They are in git and in no group.`);
|
|
97
|
+
}
|
|
98
|
+
if (review.coverage.staleCount > 0) {
|
|
99
|
+
lines.push(`Stale hunk refs: ${review.coverage.staleCount}. Live git wins. The pointer is flagged, not replaced.`);
|
|
100
|
+
}
|
|
101
|
+
return lines.length === 0 ? null : lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
function reviewConcernsBlock(review) {
|
|
104
|
+
const sections = review.groups.map((group, i) => {
|
|
105
|
+
const href = groupAgentMdHref(group.id);
|
|
106
|
+
return joinBlocks([
|
|
107
|
+
`### ${padIndex(i + 1)} ${group.title} (\`${group.id}\`)`,
|
|
108
|
+
group.summary,
|
|
109
|
+
dependsOnBlock(review, group.dependsOn),
|
|
110
|
+
`[${href}](${href})`,
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
return ["## Review concerns", ...sections].join("\n\n");
|
|
114
|
+
}
|
|
115
|
+
function lookForBlock(lookFor) {
|
|
116
|
+
if (lookFor.length === 0) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return ["Look for:", ...lookFor.map((item) => `- ${item}`)].join("\n");
|
|
120
|
+
}
|
|
121
|
+
function dependsOnBlock(review, dependsOn) {
|
|
122
|
+
if (dependsOn.length === 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const lines = dependsOn.map((id) => {
|
|
126
|
+
const dep = review.groups.find((group) => group.id === id);
|
|
127
|
+
if (dep === undefined) {
|
|
128
|
+
return `- ${id}`;
|
|
129
|
+
}
|
|
130
|
+
const index = review.groups.findIndex((group) => group.id === id) + 1;
|
|
131
|
+
return `- ${padIndex(index)} ${dep.title} (\`${dep.id}\`)`;
|
|
132
|
+
});
|
|
133
|
+
return ["Depends on:", ...lines].join("\n");
|
|
134
|
+
}
|
|
135
|
+
function hunkList(heading, hunks) {
|
|
136
|
+
if (hunks.length === 0) {
|
|
137
|
+
return `${heading}\n(none)`;
|
|
138
|
+
}
|
|
139
|
+
return [heading, ...hunks.map((hunk) => `- ${formatHunkRef(hunk)}`)].join("\n");
|
|
140
|
+
}
|
|
141
|
+
function imageNote(hunks) {
|
|
142
|
+
if (!hunks.some(isImageSlot)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return "Hunk refs with @@ -0,0 +0,0 @@ are image or binary slots. Identify those by path.";
|
|
146
|
+
}
|
|
147
|
+
function overviewSteps(review) {
|
|
148
|
+
return [
|
|
149
|
+
"## Steps",
|
|
150
|
+
"",
|
|
151
|
+
"When no question follows this paste, explain this change.",
|
|
152
|
+
"",
|
|
153
|
+
...resolveShaSteps(review),
|
|
154
|
+
"",
|
|
155
|
+
"2. Choose the relevant review concerns.",
|
|
156
|
+
" Read Review concerns. Fetch a concern file only when that concern is relevant to the question.",
|
|
157
|
+
" Done when every concern the question touches has its markdown loaded.",
|
|
158
|
+
"",
|
|
159
|
+
"3. Answer from live git.",
|
|
160
|
+
" Follow those files. Use the why and the what as interpretation. Live git wins when they disagree.",
|
|
161
|
+
` ${showCodeRule()}`,
|
|
162
|
+
" Done when the answer quotes the live code.",
|
|
163
|
+
].join("\n");
|
|
164
|
+
}
|
|
165
|
+
function groupSteps(review) {
|
|
166
|
+
const { baseSha, headSha } = review.resolved;
|
|
167
|
+
return [
|
|
168
|
+
"## Steps",
|
|
169
|
+
"",
|
|
170
|
+
"When no question follows this paste, explain this review concern.",
|
|
171
|
+
"",
|
|
172
|
+
...resolveShaSteps(review),
|
|
173
|
+
"",
|
|
174
|
+
"2. Load the hunks.",
|
|
175
|
+
" A hunk ref is a pointer into the live git diff at the pinned SHAs.",
|
|
176
|
+
` For each hunk ref, run \`git diff --find-renames ${baseSha} ${headSha} -- <path>\` and keep the hunk whose header matches the @@ range.`,
|
|
177
|
+
" Done when every hunk ref has a matching live hunk.",
|
|
178
|
+
"",
|
|
179
|
+
"3. Answer from live git.",
|
|
180
|
+
" Read those hunks. Use the why and the what as interpretation. Live git wins when they disagree.",
|
|
181
|
+
` ${showCodeRule()}`,
|
|
182
|
+
" Done when the answer quotes the live code.",
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
function resolveShaSteps(review) {
|
|
186
|
+
const { baseSha, headSha } = review.resolved;
|
|
187
|
+
return [
|
|
188
|
+
"1. Resolve the pinned SHAs.",
|
|
189
|
+
` Run \`git rev-parse --verify ${baseSha}\` and \`git rev-parse --verify ${headSha}\` in this repository.`,
|
|
190
|
+
" Done when both objects exist.",
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
function showCodeRule() {
|
|
194
|
+
return "When you show code, quote the live git lines.";
|
|
195
|
+
}
|
|
196
|
+
function padIndex(index) {
|
|
197
|
+
return String(index).padStart(2, "0");
|
|
198
|
+
}
|
|
199
|
+
function sizeLabel(size) {
|
|
200
|
+
return size.replace("-", " ");
|
|
201
|
+
}
|
|
202
|
+
function joinBlocks(parts) {
|
|
203
|
+
return parts.filter((part) => part !== null && part !== undefined && part.length > 0).join("\n\n");
|
|
204
|
+
}
|
package/dist/api/live.js
CHANGED
|
@@ -4,10 +4,11 @@ import { readImageBlob } from "../git/blob.js";
|
|
|
4
4
|
import { fileLanguage, filePatchFromGit, readPathDiff, toHunkRef } from "../git/diff.js";
|
|
5
5
|
import { GitError } from "../git/exec.js";
|
|
6
6
|
import { listCommits } from "../git/log.js";
|
|
7
|
-
import { pinRange } from "../git/repo.js";
|
|
7
|
+
import { pinRange, readRepoIdentity } from "../git/repo.js";
|
|
8
8
|
import { showFile } from "../git/show.js";
|
|
9
9
|
import { coverReview } from "../review/coverage.js";
|
|
10
10
|
import { isLockfilePath } from "../schema/lockfile.js";
|
|
11
|
+
import { AGENT_MD_MEDIA_TYPE, agentMd } from "./agent-md.js";
|
|
11
12
|
import { ApiError } from "./error.js";
|
|
12
13
|
export async function pinReviewSource(cwd, dataPath) {
|
|
13
14
|
const document = await loadDocument(dataPath);
|
|
@@ -18,9 +19,11 @@ export async function openReview(cwd, dataPath, pin) {
|
|
|
18
19
|
const range = pin ?? (await pinRange(cwd, document.source.baseRef, document.source.headRef));
|
|
19
20
|
const { files, coverage } = await coverReview(cwd, document, range);
|
|
20
21
|
const commits = await listCommits(cwd, range.baseSha, range.headSha);
|
|
22
|
+
const repo = await readRepoIdentity(cwd);
|
|
21
23
|
return {
|
|
22
24
|
cwd,
|
|
23
25
|
document,
|
|
26
|
+
repo,
|
|
24
27
|
resolved: {
|
|
25
28
|
baseRef: document.source.baseRef,
|
|
26
29
|
headRef: document.source.headRef,
|
|
@@ -35,10 +38,11 @@ export async function openReview(cwd, dataPath, pin) {
|
|
|
35
38
|
};
|
|
36
39
|
}
|
|
37
40
|
export function reviewPayload(ctx) {
|
|
38
|
-
const { document, resolved, files, coverage, commits } = ctx;
|
|
41
|
+
const { document, repo, resolved, files, coverage, commits } = ctx;
|
|
39
42
|
const lockfiles = lockfileFiles(files);
|
|
40
43
|
return {
|
|
41
44
|
document,
|
|
45
|
+
repo,
|
|
42
46
|
resolved,
|
|
43
47
|
coverage: {
|
|
44
48
|
totalHunks: coverage.totalHunks,
|
|
@@ -156,6 +160,14 @@ export async function renderResource(ctx, resource) {
|
|
|
156
160
|
switch (resource.kind) {
|
|
157
161
|
case "review":
|
|
158
162
|
return { encoding: "json", body: reviewPayload(ctx) };
|
|
163
|
+
case "agent-md": {
|
|
164
|
+
const body = agentMd(reviewPayload(ctx), resource);
|
|
165
|
+
if (body === null) {
|
|
166
|
+
const id = resource.target === "group" ? resource.group : "overview";
|
|
167
|
+
throw new ApiError(404, `unknown group "${id}"`);
|
|
168
|
+
}
|
|
169
|
+
return { encoding: "bytes", mediaType: AGENT_MD_MEDIA_TYPE, body: Buffer.from(body, "utf8") };
|
|
170
|
+
}
|
|
159
171
|
case "hunks":
|
|
160
172
|
return { encoding: "json", body: hunksPayload(ctx, resource.group) };
|
|
161
173
|
case "file":
|
|
@@ -171,11 +183,13 @@ export async function renderResource(ctx, resource) {
|
|
|
171
183
|
export function listResources(ctx) {
|
|
172
184
|
const resources = [
|
|
173
185
|
{ kind: "review" },
|
|
186
|
+
{ kind: "agent-md", target: "overview" },
|
|
174
187
|
{ kind: "hunks", group: "unassigned" },
|
|
175
188
|
{ kind: "hunks", group: "lockfiles" },
|
|
176
189
|
];
|
|
177
190
|
for (const group of ctx.document.groups) {
|
|
178
191
|
resources.push({ kind: "hunks", group: group.id });
|
|
192
|
+
resources.push({ kind: "agent-md", target: "group", group: group.id });
|
|
179
193
|
}
|
|
180
194
|
for (const file of ctx.files) {
|
|
181
195
|
if (file.image) {
|
package/dist/api/paths.js
CHANGED
|
@@ -2,6 +2,8 @@ export function apiHref(resource) {
|
|
|
2
2
|
switch (resource.kind) {
|
|
3
3
|
case "review":
|
|
4
4
|
return "api/review.json";
|
|
5
|
+
case "agent-md":
|
|
6
|
+
return agentMdRel(resource);
|
|
5
7
|
case "hunks":
|
|
6
8
|
return `api/hunks/${encodeURIComponent(resource.group)}.json`;
|
|
7
9
|
case "file":
|
|
@@ -19,6 +21,8 @@ export function apiFsRel(resource) {
|
|
|
19
21
|
switch (resource.kind) {
|
|
20
22
|
case "review":
|
|
21
23
|
return "api/review.json";
|
|
24
|
+
case "agent-md":
|
|
25
|
+
return agentMdRel(resource);
|
|
22
26
|
case "hunks":
|
|
23
27
|
return `api/hunks/${encodeURIComponent(resource.group)}.json`;
|
|
24
28
|
case "file":
|
|
@@ -39,6 +43,19 @@ export function parseApiPath(pathname) {
|
|
|
39
43
|
if (parts[1] === "review.json" && parts.length === 2) {
|
|
40
44
|
return { kind: "review" };
|
|
41
45
|
}
|
|
46
|
+
if (parts[1] === "agent") {
|
|
47
|
+
if (parts.length === 3 && parts[2] === "overview.md") {
|
|
48
|
+
return { kind: "agent-md", target: "overview" };
|
|
49
|
+
}
|
|
50
|
+
if (parts.length === 4 && parts[2] === "groups" && parts[3] !== undefined) {
|
|
51
|
+
const group = mdStem(parts[3]);
|
|
52
|
+
if (group === undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return { kind: "agent-md", target: "group", group };
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
42
59
|
if (parts[1] === "hunks" && parts.length === 3 && parts[2] !== undefined) {
|
|
43
60
|
const group = jsonStem(parts[2]);
|
|
44
61
|
if (group === undefined) {
|
|
@@ -102,6 +119,12 @@ function decodeSegment(segment) {
|
|
|
102
119
|
return segment;
|
|
103
120
|
}
|
|
104
121
|
}
|
|
122
|
+
function agentMdRel(resource) {
|
|
123
|
+
if (resource.target === "overview") {
|
|
124
|
+
return "api/agent/overview.md";
|
|
125
|
+
}
|
|
126
|
+
return `api/agent/groups/${encodeURIComponent(resource.group)}.md`;
|
|
127
|
+
}
|
|
105
128
|
function jsonStem(file) {
|
|
106
129
|
if (!file.endsWith(".json")) {
|
|
107
130
|
return undefined;
|
|
@@ -109,6 +132,13 @@ function jsonStem(file) {
|
|
|
109
132
|
const stem = file.slice(0, -".json".length);
|
|
110
133
|
return stem === "" ? undefined : stem;
|
|
111
134
|
}
|
|
135
|
+
function mdStem(file) {
|
|
136
|
+
if (!file.endsWith(".md")) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const stem = file.slice(0, -".md".length);
|
|
140
|
+
return stem === "" ? undefined : stem;
|
|
141
|
+
}
|
|
112
142
|
function isRepoPath(path) {
|
|
113
143
|
return path !== "" && !path.startsWith("/") && !path.includes("\0") && !path.split("/").includes("..");
|
|
114
144
|
}
|
package/dist/git/repo.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
1
2
|
import { git, gitOk } from "./exec.js";
|
|
2
3
|
export async function assertWorkTree(cwd) {
|
|
3
4
|
const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], { allowFail: true });
|
|
@@ -14,6 +15,47 @@ export async function mergeBase(cwd, baseRef, headRef) {
|
|
|
14
15
|
const sha = await git(cwd, ["merge-base", baseRef, headRef]);
|
|
15
16
|
return sha.trim();
|
|
16
17
|
}
|
|
18
|
+
/** Last path segment of a git remote URL, without .git. */
|
|
19
|
+
export function nameFromRemoteUrl(url) {
|
|
20
|
+
let value = url.trim();
|
|
21
|
+
if (value === "") {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
value = value.replace(/\.git$/i, "").replace(/\/+$/, "");
|
|
25
|
+
const pathPart = value.includes("://")
|
|
26
|
+
? value.replace(/^[^:]+:\/\/[^/]+\//, "")
|
|
27
|
+
: value.includes(":")
|
|
28
|
+
? value.slice(value.lastIndexOf(":") + 1)
|
|
29
|
+
: value;
|
|
30
|
+
const last = pathPart.split("/").filter(Boolean).at(-1);
|
|
31
|
+
return last === undefined || last === "" ? null : last;
|
|
32
|
+
}
|
|
33
|
+
export async function readRepoIdentity(cwd) {
|
|
34
|
+
const originText = (await git(cwd, ["config", "--get", "remote.origin.url"], { allowFail: true })).trim();
|
|
35
|
+
const origin = originText === "" ? null : stripRemoteCredentials(originText);
|
|
36
|
+
const top = (await git(cwd, ["rev-parse", "--show-toplevel"])).trim();
|
|
37
|
+
const fromOrigin = origin !== null ? nameFromRemoteUrl(origin) : null;
|
|
38
|
+
return { name: fromOrigin ?? basename(top), origin };
|
|
39
|
+
}
|
|
40
|
+
/** Drop userinfo from an http(s) remote so a copied prompt never carries a token. */
|
|
41
|
+
export function stripRemoteCredentials(url) {
|
|
42
|
+
const trimmed = url.trim();
|
|
43
|
+
if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed)) {
|
|
44
|
+
return trimmed;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const parsed = new URL(trimmed);
|
|
48
|
+
if (parsed.username === "" && parsed.password === "") {
|
|
49
|
+
return trimmed;
|
|
50
|
+
}
|
|
51
|
+
parsed.username = "";
|
|
52
|
+
parsed.password = "";
|
|
53
|
+
return parsed.toString().replace(/\/$/, "");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return trimmed;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
17
59
|
/** Resolve refs to commits once. Later checkout or branch motion does not move these SHAs. */
|
|
18
60
|
export async function pinRange(cwd, baseRef, headRef) {
|
|
19
61
|
const baseSha = await resolveCommit(cwd, baseRef);
|
package/dist/server/http.js
CHANGED
|
@@ -11,6 +11,7 @@ const MIME = {
|
|
|
11
11
|
".js": "text/javascript; charset=utf-8",
|
|
12
12
|
".css": "text/css; charset=utf-8",
|
|
13
13
|
".json": "application/json; charset=utf-8",
|
|
14
|
+
".md": "text/markdown; charset=utf-8",
|
|
14
15
|
".svg": "image/svg+xml",
|
|
15
16
|
".map": "application/json; charset=utf-8",
|
|
16
17
|
".woff2": "font/woff2",
|