prtape-cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +138 -2
- package/dist/adapters/claude.d.ts +2 -0
- package/dist/adapters/claude.js +309 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/bash-edits.d.ts +41 -0
- package/dist/bash-edits.js +440 -0
- package/dist/bash-edits.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +214 -0
- package/dist/cli.js.map +1 -0
- package/dist/decision.d.ts +7 -0
- package/dist/decision.js +224 -0
- package/dist/decision.js.map +1 -0
- package/dist/discover.d.ts +32 -0
- package/dist/discover.js +179 -0
- package/dist/discover.js.map +1 -0
- package/dist/display.d.ts +28 -0
- package/dist/display.js +311 -0
- package/dist/display.js.map +1 -0
- package/dist/env.d.ts +5 -0
- package/dist/env.js +11 -0
- package/dist/env.js.map +1 -0
- package/dist/gh.d.ts +46 -0
- package/dist/gh.js +190 -0
- package/dist/gh.js.map +1 -0
- package/dist/glance.d.ts +15 -0
- package/dist/glance.js +75 -0
- package/dist/glance.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/orphan.d.ts +44 -0
- package/dist/orphan.js +152 -0
- package/dist/orphan.js.map +1 -0
- package/dist/png.d.ts +2 -0
- package/dist/png.js +13 -0
- package/dist/png.js.map +1 -0
- package/dist/privacy.d.ts +7 -0
- package/dist/privacy.js +39 -0
- package/dist/privacy.js.map +1 -0
- package/dist/redact.d.ts +17 -0
- package/dist/redact.js +101 -0
- package/dist/redact.js.map +1 -0
- package/dist/run.d.ts +48 -0
- package/dist/run.js +218 -0
- package/dist/run.js.map +1 -0
- package/dist/summary.d.ts +7 -0
- package/dist/summary.js +134 -0
- package/dist/summary.js.map +1 -0
- package/dist/tests-detect.d.ts +19 -0
- package/dist/tests-detect.js +97 -0
- package/dist/tests-detect.js.map +1 -0
- package/dist/text.d.ts +8 -0
- package/dist/text.js +31 -0
- package/dist/text.js.map +1 -0
- package/dist/timeline/format.d.ts +28 -0
- package/dist/timeline/format.js +127 -0
- package/dist/timeline/format.js.map +1 -0
- package/dist/timeline/md.d.ts +17 -0
- package/dist/timeline/md.js +129 -0
- package/dist/timeline/md.js.map +1 -0
- package/dist/timeline/svg.d.ts +7 -0
- package/dist/timeline/svg.js +260 -0
- package/dist/timeline/svg.js.map +1 -0
- package/dist/timeline/wrap.d.ts +7 -0
- package/dist/timeline/wrap.js +77 -0
- package/dist/timeline/wrap.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -4
package/dist/run.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parseClaude } from "./adapters/claude.js";
|
|
6
|
+
import { defaultClaudeHome, findSession, NoSessionError, } from "./discover.js";
|
|
7
|
+
import { presentSummary } from "./display.js";
|
|
8
|
+
import { glanceRaster } from "./glance.js";
|
|
9
|
+
import { GhError, postComment, pullRequestHead } from "./gh.js";
|
|
10
|
+
import { hostTimelinePng } from "./orphan.js";
|
|
11
|
+
import { renderPng } from "./png.js";
|
|
12
|
+
import { sessionIsEmpty } from "./summary.js";
|
|
13
|
+
import { renderComment, terminalSummary } from "./timeline/md.js";
|
|
14
|
+
import { publicSteps } from "./timeline/format.js";
|
|
15
|
+
import { renderSvg } from "./timeline/svg.js";
|
|
16
|
+
import { VERSION } from "./version.js";
|
|
17
|
+
export function toPublicJson(summary, image = { imageHosted: false }) {
|
|
18
|
+
return {
|
|
19
|
+
version: VERSION,
|
|
20
|
+
source: summary.source,
|
|
21
|
+
startedAt: summary.startedAt ?? null,
|
|
22
|
+
model: summary.model ?? null,
|
|
23
|
+
branch: summary.branch ?? null,
|
|
24
|
+
title: summary.title,
|
|
25
|
+
decision: summary.decision,
|
|
26
|
+
testsRun: summary.testsRun,
|
|
27
|
+
tests: summary.tests,
|
|
28
|
+
files: summary.files.filter((file) => isRepoRelative(file.path)),
|
|
29
|
+
steps: publicSteps(summary.steps.flatMap((step) => {
|
|
30
|
+
if ((step.kind === "read" || step.kind === "edit") && step.path && !isRepoRelative(step.path)) {
|
|
31
|
+
return step.kind === "read" ? [{ ...step, path: undefined }] : [];
|
|
32
|
+
}
|
|
33
|
+
return [step];
|
|
34
|
+
})),
|
|
35
|
+
imageHosted: image.imageHosted,
|
|
36
|
+
imageUrl: image.imageUrl ?? null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export async function runPr(options) {
|
|
40
|
+
const cwd = path.resolve(options.cwd);
|
|
41
|
+
const found = locate(options, cwd);
|
|
42
|
+
const raw = readSession(found.path);
|
|
43
|
+
const summary = parseFound(found.path, raw);
|
|
44
|
+
if (!summary.branch) {
|
|
45
|
+
const branch = gitBranch(cwd);
|
|
46
|
+
if (branch)
|
|
47
|
+
summary.branch = branch;
|
|
48
|
+
}
|
|
49
|
+
if (!options.dryRun) {
|
|
50
|
+
const head = await pullRequestHead(cwd, options.runner, options.prNumber);
|
|
51
|
+
if (head)
|
|
52
|
+
summary.branch = head;
|
|
53
|
+
}
|
|
54
|
+
if (sessionIsEmpty(summary)) {
|
|
55
|
+
throw new NoSessionError(`Nothing to timeline in ${found.path}. No prompt, edit, test, or summary was recognized.`);
|
|
56
|
+
}
|
|
57
|
+
const redactOptions = { emails: options.redactEmails };
|
|
58
|
+
const view = presentSummary(summary, redactOptions);
|
|
59
|
+
const shown = view.summary;
|
|
60
|
+
const svg = renderSvg(summary, redactOptions, options.demo === true);
|
|
61
|
+
const tmpDir = options.tmpDir ?? os.tmpdir();
|
|
62
|
+
const svgPath = writeTimelineFile(svg, cwd, summary.sessionId, tmpDir, "svg");
|
|
63
|
+
const raster = rasterize(svg);
|
|
64
|
+
const pngPath = raster.png
|
|
65
|
+
? writeTimelineFile(raster.png, cwd, summary.sessionId, tmpDir, "png")
|
|
66
|
+
: undefined;
|
|
67
|
+
if (options.dryRun) {
|
|
68
|
+
return {
|
|
69
|
+
summary: shown,
|
|
70
|
+
comment: pngPath
|
|
71
|
+
? renderComment(summary, redactOptions, { url: "timeline.png", isPrivate: false, dryRun: true }, options.demo === true)
|
|
72
|
+
: renderComment(summary, redactOptions, undefined, options.demo === true),
|
|
73
|
+
svg,
|
|
74
|
+
svgPath,
|
|
75
|
+
pngPath,
|
|
76
|
+
posted: false,
|
|
77
|
+
imageHosted: false,
|
|
78
|
+
sessionNote: found.note,
|
|
79
|
+
origin: found.path,
|
|
80
|
+
note: [
|
|
81
|
+
"PRtape dry-run. Nothing was posted and nothing was pushed.",
|
|
82
|
+
"A real run pushes the PNG to refs/prtape/<head> and the comment leads with https://github.com/{owner}/{repo}/raw/{sha}/timeline.png.",
|
|
83
|
+
"Logged-out viewers see that image on a public repo. On a private repo they may not.",
|
|
84
|
+
].join("\n"),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const hosted = await resolveHost(options, cwd, raster);
|
|
88
|
+
const comment = hosted.ok
|
|
89
|
+
? renderComment(summary, redactOptions, { url: hosted.url, isPrivate: hosted.isPrivate }, options.demo === true)
|
|
90
|
+
: renderComment(summary, redactOptions, undefined, options.demo === true);
|
|
91
|
+
const base = {
|
|
92
|
+
summary: shown,
|
|
93
|
+
comment,
|
|
94
|
+
svg,
|
|
95
|
+
svgPath,
|
|
96
|
+
pngPath,
|
|
97
|
+
sessionNote: found.note,
|
|
98
|
+
origin: found.path,
|
|
99
|
+
imageHosted: hosted.ok,
|
|
100
|
+
imageUrl: hosted.ok ? hosted.url : undefined,
|
|
101
|
+
};
|
|
102
|
+
if (!hosted.ok && blocksComment(hosted.glance)) {
|
|
103
|
+
return { ...base, posted: false, note: `${hosted.glance}\nNothing was posted.` };
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const pr = await postComment(cwd, comment, options.runner, options.prNumber);
|
|
107
|
+
if (!hosted.ok) {
|
|
108
|
+
return {
|
|
109
|
+
...base,
|
|
110
|
+
posted: true,
|
|
111
|
+
prNumber: pr.number,
|
|
112
|
+
prUrl: pr.url || undefined,
|
|
113
|
+
note: `${hosted.glance}\n${pr.updated ? "Updated" : "Posted"} the text timeline on PR #${pr.number}. The PNG is not in the comment.`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
...base,
|
|
118
|
+
posted: true,
|
|
119
|
+
prNumber: pr.number,
|
|
120
|
+
prUrl: pr.url || undefined,
|
|
121
|
+
note: `${pr.updated ? "Updated" : "Commented on"} PR #${pr.number} with the hosted PNG.\n${hosted.url}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const reason = error instanceof GhError ? error.message : "Could not post the comment.";
|
|
126
|
+
const imageLine = hosted.ok
|
|
127
|
+
? `The PNG is on ${hosted.ref} at ${hosted.url}, but the comment was not posted.`
|
|
128
|
+
: hosted.glance;
|
|
129
|
+
return { ...base, posted: false, note: `${imageLine}\n${reason} Nothing was posted.` };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function rasterize(svg) {
|
|
133
|
+
try {
|
|
134
|
+
return { png: renderPng(svg) };
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return { glance: glanceRaster() };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function resolveHost(options, cwd, raster) {
|
|
141
|
+
if (!raster.png)
|
|
142
|
+
return { ok: false, glance: raster.glance ?? glanceRaster() };
|
|
143
|
+
if (options.hostImage)
|
|
144
|
+
return options.hostImage({ cwd, png: raster.png });
|
|
145
|
+
return hostTimelinePng({ cwd, png: raster.png, runner: options.runner, tmpRoot: options.tmpDir });
|
|
146
|
+
}
|
|
147
|
+
function blocksComment(glance) {
|
|
148
|
+
return /gh is not installed|gh is not authenticated/.test(glance);
|
|
149
|
+
}
|
|
150
|
+
export function formatPosted(result) {
|
|
151
|
+
const lines = [result.note];
|
|
152
|
+
if (result.prUrl)
|
|
153
|
+
lines.push(result.prUrl);
|
|
154
|
+
lines.push(`SVG: ${result.svgPath}`);
|
|
155
|
+
if (result.pngPath)
|
|
156
|
+
lines.push(`PNG: ${result.pngPath}`);
|
|
157
|
+
lines.push("");
|
|
158
|
+
lines.push(terminalSummary(result.summary));
|
|
159
|
+
return `${lines.join("\n")}\n`;
|
|
160
|
+
}
|
|
161
|
+
function locate(options, cwd) {
|
|
162
|
+
if (options.sessionPath) {
|
|
163
|
+
const file = path.resolve(options.sessionPath);
|
|
164
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
165
|
+
throw new NoSessionError(`Session file not found: ${file}`);
|
|
166
|
+
}
|
|
167
|
+
return { source: "claude-code", path: file };
|
|
168
|
+
}
|
|
169
|
+
const discover = {
|
|
170
|
+
cwd,
|
|
171
|
+
source: options.source,
|
|
172
|
+
claudeHome: options.claudeHome ?? defaultClaudeHome(),
|
|
173
|
+
recentFiles: options.recentFiles,
|
|
174
|
+
};
|
|
175
|
+
return findSession(discover);
|
|
176
|
+
}
|
|
177
|
+
function parseFound(file, raw) {
|
|
178
|
+
return parseClaude(raw, path.basename(file));
|
|
179
|
+
}
|
|
180
|
+
/** A path a reviewer can place inside the repository. Home leftovers and absolutes stay out. */
|
|
181
|
+
function isRepoRelative(filePath) {
|
|
182
|
+
const value = filePath.replace(/\\/g, "/").trim();
|
|
183
|
+
if (!value || value.startsWith("/") || /^[A-Za-z]:\//.test(value))
|
|
184
|
+
return false;
|
|
185
|
+
if (value === ".." || value.startsWith("../") || value.includes("/../"))
|
|
186
|
+
return false;
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
function readSession(file) {
|
|
190
|
+
return fs.readFileSync(file, "utf8");
|
|
191
|
+
}
|
|
192
|
+
export function writeTimelineFile(contents, cwd, sessionId, tmpDir, extension) {
|
|
193
|
+
const safe = sessionId.replace(/[^A-Za-z0-9_-]+/g, "").slice(0, 48) || "session";
|
|
194
|
+
const file = path.join(tmpDir, `prtape-${safe}.${extension}`);
|
|
195
|
+
assertNotInsideProject(file, cwd);
|
|
196
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
197
|
+
fs.writeFileSync(file, contents, { mode: 0o600 });
|
|
198
|
+
return file;
|
|
199
|
+
}
|
|
200
|
+
export function assertNotInsideProject(target, cwd) {
|
|
201
|
+
const file = path.resolve(target);
|
|
202
|
+
const root = path.resolve(cwd);
|
|
203
|
+
const relative = path.relative(root, file);
|
|
204
|
+
const inside = relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
205
|
+
if (inside) {
|
|
206
|
+
throw new Error(`Refusing to write the timeline inside the project tree (${file}).`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function gitBranch(cwd) {
|
|
210
|
+
const result = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf8" });
|
|
211
|
+
if (result.status !== 0)
|
|
212
|
+
return undefined;
|
|
213
|
+
const name = result.stdout.trim();
|
|
214
|
+
if (!name || name === "HEAD")
|
|
215
|
+
return undefined;
|
|
216
|
+
return name;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=run.js.map
|
package/dist/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionDraft, SessionSummary, TestOutcome, TestRun } from "./types.js";
|
|
2
|
+
export declare function withSteps(draft: SessionDraft): SessionSummary;
|
|
3
|
+
export declare function addFile(files: SessionDraft["files"], filePath: string, action: SessionDraft["files"][number]["action"]): void;
|
|
4
|
+
/** Keep every run, in order, so a later pass does not erase the earlier failure. */
|
|
5
|
+
export declare function addTest(tests: TestRun[], command: string, outcome: TestOutcome): void;
|
|
6
|
+
export declare function sessionIsEmpty(summary: SessionSummary): boolean;
|
|
7
|
+
export declare function rollupTests(tests: TestRun[]): "pass" | "fail" | "unknown" | "none";
|
package/dist/summary.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { displayTestCommand } from "./tests-detect.js";
|
|
2
|
+
import { FALLBACK_DECISION, FALLBACK_TITLE } from "./types.js";
|
|
3
|
+
const FILE_RANK = { edit: 1, delete: 2, write: 3 };
|
|
4
|
+
export function withSteps(draft) {
|
|
5
|
+
const title = draft.title.trim() || FALLBACK_TITLE;
|
|
6
|
+
const decision = draft.decision.trim() || FALLBACK_DECISION;
|
|
7
|
+
const events = collapseIdenticalFails(draft.events ?? bucketEvents(draft.files, draft.tests));
|
|
8
|
+
const files = draft.events ? filesFrom(events) : draft.files;
|
|
9
|
+
const tests = draft.events ? testsFrom(events) : draft.tests;
|
|
10
|
+
const steps = [
|
|
11
|
+
{ kind: "prompt", text: title },
|
|
12
|
+
...events.map((event) => eventStep(event)),
|
|
13
|
+
{ kind: "decision", text: decision },
|
|
14
|
+
];
|
|
15
|
+
return {
|
|
16
|
+
...draft,
|
|
17
|
+
title,
|
|
18
|
+
decision,
|
|
19
|
+
files,
|
|
20
|
+
tests,
|
|
21
|
+
events,
|
|
22
|
+
testsRun: tests.length > 0,
|
|
23
|
+
steps,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Drop a second record of the same failing command.
|
|
28
|
+
* Adjacent duplicates are one tool call stored twice.
|
|
29
|
+
* A failing test bundled with the inspection reads that follow an earlier
|
|
30
|
+
* failure of that same command is the same run, not a new attempt.
|
|
31
|
+
* A later failure after a separate read stays.
|
|
32
|
+
*/
|
|
33
|
+
function collapseIdenticalFails(events) {
|
|
34
|
+
const kept = [];
|
|
35
|
+
for (const event of events) {
|
|
36
|
+
if (isDuplicateFail(kept, event))
|
|
37
|
+
continue;
|
|
38
|
+
kept.push(event);
|
|
39
|
+
}
|
|
40
|
+
return kept;
|
|
41
|
+
}
|
|
42
|
+
function isDuplicateFail(kept, event) {
|
|
43
|
+
if (event.kind !== "test" || event.outcome !== "fail")
|
|
44
|
+
return false;
|
|
45
|
+
const command = displayTestCommand(event.command);
|
|
46
|
+
if (!command)
|
|
47
|
+
return false;
|
|
48
|
+
const previous = kept.at(-1);
|
|
49
|
+
if (previous?.kind === "test" && previous.outcome === "fail" && displayTestCommand(previous.command) === command) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (!event.batch)
|
|
53
|
+
return false;
|
|
54
|
+
let index = kept.length - 1;
|
|
55
|
+
while (index >= 0 && kept[index]?.kind === "read" && kept[index]?.batch === event.batch)
|
|
56
|
+
index -= 1;
|
|
57
|
+
if (index === kept.length - 1)
|
|
58
|
+
return false;
|
|
59
|
+
const earlier = kept[index];
|
|
60
|
+
return earlier?.kind === "test" && earlier.outcome === "fail" && displayTestCommand(earlier.command) === command;
|
|
61
|
+
}
|
|
62
|
+
/** Files then tests. Used when a parser has no interleaved event list. */
|
|
63
|
+
function bucketEvents(files, tests) {
|
|
64
|
+
return [
|
|
65
|
+
...files.map((file) => ({ kind: "edit", path: file.path, action: file.action })),
|
|
66
|
+
...tests.map((test) => ({ kind: "test", command: test.command, outcome: test.outcome })),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
function filesFrom(events) {
|
|
70
|
+
const files = [];
|
|
71
|
+
for (const event of events) {
|
|
72
|
+
if (event.kind === "edit")
|
|
73
|
+
addFile(files, event.path, event.action);
|
|
74
|
+
}
|
|
75
|
+
return files;
|
|
76
|
+
}
|
|
77
|
+
function testsFrom(events) {
|
|
78
|
+
return events
|
|
79
|
+
.filter((event) => event.kind === "test")
|
|
80
|
+
.map((event) => ({
|
|
81
|
+
command: event.command,
|
|
82
|
+
outcome: event.outcome,
|
|
83
|
+
failed: event.failed,
|
|
84
|
+
passed: event.passed,
|
|
85
|
+
total: event.total,
|
|
86
|
+
failedName: event.failedName,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
function eventStep(event) {
|
|
90
|
+
switch (event.kind) {
|
|
91
|
+
case "read":
|
|
92
|
+
return { kind: "read", path: event.path };
|
|
93
|
+
case "edit":
|
|
94
|
+
return { kind: "edit", path: event.path, action: event.action };
|
|
95
|
+
case "test":
|
|
96
|
+
return { kind: "test", command: event.command, outcome: event.outcome };
|
|
97
|
+
case "commit":
|
|
98
|
+
return { kind: "commit" };
|
|
99
|
+
default: {
|
|
100
|
+
const never = event;
|
|
101
|
+
return never;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function addFile(files, filePath, action) {
|
|
106
|
+
const existing = files.find((file) => file.path === filePath);
|
|
107
|
+
if (!existing) {
|
|
108
|
+
files.push({ path: filePath, action });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (FILE_RANK[action] > FILE_RANK[existing.action]) {
|
|
112
|
+
existing.action = action;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** Keep every run, in order, so a later pass does not erase the earlier failure. */
|
|
116
|
+
export function addTest(tests, command, outcome) {
|
|
117
|
+
const key = command.trim();
|
|
118
|
+
if (!key)
|
|
119
|
+
return;
|
|
120
|
+
tests.push({ command: key, outcome });
|
|
121
|
+
}
|
|
122
|
+
export function sessionIsEmpty(summary) {
|
|
123
|
+
return (summary.title === FALLBACK_TITLE &&
|
|
124
|
+
summary.decision === FALLBACK_DECISION &&
|
|
125
|
+
summary.files.length === 0 &&
|
|
126
|
+
summary.tests.length === 0);
|
|
127
|
+
}
|
|
128
|
+
export function rollupTests(tests) {
|
|
129
|
+
const last = tests.at(-1);
|
|
130
|
+
if (!last)
|
|
131
|
+
return "none";
|
|
132
|
+
return last.outcome;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TestOutcome } from "./types.js";
|
|
2
|
+
export declare function isTestCommand(command: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Node's built-in TAP reporter prints a summary even when the shell pipeline
|
|
5
|
+
* exits 0 (`npm test | tee log`). Those counts decide the outcome. A successful
|
|
6
|
+
* `tee` is not a passing suite.
|
|
7
|
+
*/
|
|
8
|
+
export declare function classifyNodeTap(output: string): TestOutcome | undefined;
|
|
9
|
+
export declare function classifyTestOutput(output: string, isError: boolean, interrupted?: boolean): TestOutcome;
|
|
10
|
+
export interface ParsedTestCounts {
|
|
11
|
+
failed?: number;
|
|
12
|
+
passed?: number;
|
|
13
|
+
total?: number;
|
|
14
|
+
failedName?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Counts and the first failing name. Missing fields stay unset. Nothing is guessed. */
|
|
17
|
+
export declare function parseTestCounts(output: string): ParsedTestCounts;
|
|
18
|
+
/** The test invocation inside a pipeline, without tee or log paths. */
|
|
19
|
+
export declare function displayTestCommand(command: string): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands that mean "the agent ran the test suite".
|
|
3
|
+
* Matched against a shell invocation, not against prose.
|
|
4
|
+
* Leading environment assignments (`FOO=bar npm test`) are ignored.
|
|
5
|
+
*/
|
|
6
|
+
const TEST_COMMAND = /^(?:npm(?:\s+run)?\s+test|pnpm(?:\s+run)?\s+test|yarn\s+test|bun\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|vitest|jest|mocha|pytest|py\.test|python(?:3)?\s+-m\s+pytest|tox|cargo\s+test|go\s+test|mvn(?:\s+\S+)*\s+test|gradle(?:w)?(?:\s+\S+)*\s+test|ctest|make\s+test|rake\s+test|rspec|phpunit|mix\s+test|deno\s+test|dotnet\s+test)\b/;
|
|
7
|
+
export function isTestCommand(command) {
|
|
8
|
+
const flat = command.replace(/\s+/g, " ").trim();
|
|
9
|
+
const parts = flat.split(/\s*(?:&&|\|\||[;&|])\s*/);
|
|
10
|
+
return parts.some((part) => {
|
|
11
|
+
const stripped = part.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)+/, "");
|
|
12
|
+
return TEST_COMMAND.test(stripped);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Node's built-in TAP reporter prints a summary even when the shell pipeline
|
|
17
|
+
* exits 0 (`npm test | tee log`). Those counts decide the outcome. A successful
|
|
18
|
+
* `tee` is not a passing suite.
|
|
19
|
+
*/
|
|
20
|
+
export function classifyNodeTap(output) {
|
|
21
|
+
const fail = lastTapCount(output, "fail");
|
|
22
|
+
const pass = lastTapCount(output, "pass");
|
|
23
|
+
if (fail !== undefined && fail > 0)
|
|
24
|
+
return "fail";
|
|
25
|
+
if (pass !== undefined && pass > 0 && (fail === undefined || fail === 0))
|
|
26
|
+
return "pass";
|
|
27
|
+
if (/\bnot ok\b/i.test(output))
|
|
28
|
+
return "fail";
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
export function classifyTestOutput(output, isError, interrupted = false) {
|
|
32
|
+
if (interrupted)
|
|
33
|
+
return "fail";
|
|
34
|
+
const tap = classifyNodeTap(output);
|
|
35
|
+
if (tap)
|
|
36
|
+
return tap;
|
|
37
|
+
if (isError)
|
|
38
|
+
return "fail";
|
|
39
|
+
const failedCount = /(\d+)\s+failed\b/i.exec(output);
|
|
40
|
+
if (failedCount && Number(failedCount[1]) > 0)
|
|
41
|
+
return "fail";
|
|
42
|
+
if (/\b(FAIL|FAILED|FAILURES)\b/.test(output))
|
|
43
|
+
return "fail";
|
|
44
|
+
if (/\bexit code (?!0\b)\d+/i.test(output))
|
|
45
|
+
return "fail";
|
|
46
|
+
if (/\b(PASS|PASSED)\b/.test(output) ||
|
|
47
|
+
/\b\d+\s+passed\b/i.test(output) ||
|
|
48
|
+
/\bexit code 0\b/i.test(output) ||
|
|
49
|
+
(failedCount !== null && Number(failedCount[1]) === 0)) {
|
|
50
|
+
return "pass";
|
|
51
|
+
}
|
|
52
|
+
return "unknown";
|
|
53
|
+
}
|
|
54
|
+
/** Counts and the first failing name. Missing fields stay unset. Nothing is guessed. */
|
|
55
|
+
export function parseTestCounts(output) {
|
|
56
|
+
const counts = {};
|
|
57
|
+
const failed = lastTapCount(output, "fail") ?? firstNumber(output, /(\d+)\s+failed\b/i);
|
|
58
|
+
const passed = lastTapCount(output, "pass") ?? firstNumber(output, /(\d+)\s+passed\b/i);
|
|
59
|
+
const total = lastTapCount(output, "tests") ?? firstNumber(output, /(\d+)\s+total\b/i);
|
|
60
|
+
if (failed !== undefined)
|
|
61
|
+
counts.failed = failed;
|
|
62
|
+
if (passed !== undefined)
|
|
63
|
+
counts.passed = passed;
|
|
64
|
+
if (total !== undefined)
|
|
65
|
+
counts.total = total;
|
|
66
|
+
const name = /^not ok \d+ - (.+)$/m.exec(output)?.[1]?.trim();
|
|
67
|
+
if (name)
|
|
68
|
+
counts.failedName = name;
|
|
69
|
+
return counts;
|
|
70
|
+
}
|
|
71
|
+
function firstNumber(output, pattern) {
|
|
72
|
+
const match = pattern.exec(output);
|
|
73
|
+
if (!match?.[1])
|
|
74
|
+
return undefined;
|
|
75
|
+
return Number(match[1]);
|
|
76
|
+
}
|
|
77
|
+
function lastTapCount(output, label) {
|
|
78
|
+
const pattern = new RegExp(`^\\s*#\\s+${label}\\s+(\\d+)\\s*$`, "gim");
|
|
79
|
+
let last;
|
|
80
|
+
for (const match of output.matchAll(pattern)) {
|
|
81
|
+
last = Number(match[1]);
|
|
82
|
+
}
|
|
83
|
+
return last;
|
|
84
|
+
}
|
|
85
|
+
/** The test invocation inside a pipeline, without tee or log paths. */
|
|
86
|
+
export function displayTestCommand(command) {
|
|
87
|
+
const flat = command.replace(/\s+/g, " ").trim();
|
|
88
|
+
const parts = flat.split(/\s*(?:&&|\|\||[;&|])\s*/);
|
|
89
|
+
for (const part of parts) {
|
|
90
|
+
const stripped = part.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)+/, "").replace(/\s+\d*>&\d+.*$/, "").trim();
|
|
91
|
+
const match = stripped.match(TEST_COMMAND);
|
|
92
|
+
if (match?.[0])
|
|
93
|
+
return match[0];
|
|
94
|
+
}
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=tests-detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tests-detect.js","sourceRoot":"","sources":["../src/tests-detect.ts"],"names":[],"mappings":"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"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Collapse whitespace into a single line. */
|
|
2
|
+
export declare function oneLine(value: string): string;
|
|
3
|
+
/** Shorten `value` to `max` characters, counting the ellipsis. */
|
|
4
|
+
export declare function truncate(value: string, max: number): string;
|
|
5
|
+
export declare function escapeXml(value: string): string;
|
|
6
|
+
/** First sentence, used as the short "ask" on the visual timeline. */
|
|
7
|
+
export declare function firstSentence(value: string): string;
|
|
8
|
+
export declare function escapeMdCell(value: string): string;
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Collapse whitespace into a single line. */
|
|
2
|
+
export function oneLine(value) {
|
|
3
|
+
return value.replace(/\s+/g, " ").trim();
|
|
4
|
+
}
|
|
5
|
+
/** Shorten `value` to `max` characters, counting the ellipsis. */
|
|
6
|
+
export function truncate(value, max) {
|
|
7
|
+
const text = oneLine(value);
|
|
8
|
+
if (text.length <= max)
|
|
9
|
+
return text;
|
|
10
|
+
if (max <= 1)
|
|
11
|
+
return "…";
|
|
12
|
+
return `${text.slice(0, max - 1).trimEnd()}…`;
|
|
13
|
+
}
|
|
14
|
+
export function escapeXml(value) {
|
|
15
|
+
return value
|
|
16
|
+
.replace(/&/g, "&")
|
|
17
|
+
.replace(/</g, "<")
|
|
18
|
+
.replace(/>/g, ">")
|
|
19
|
+
.replace(/"/g, """)
|
|
20
|
+
.replace(/'/g, "'");
|
|
21
|
+
}
|
|
22
|
+
/** First sentence, used as the short "ask" on the visual timeline. */
|
|
23
|
+
export function firstSentence(value) {
|
|
24
|
+
const line = oneLine(value);
|
|
25
|
+
const match = /^(.+?[.!?])(?:\s|$)/.exec(line);
|
|
26
|
+
return match?.[1] ?? line;
|
|
27
|
+
}
|
|
28
|
+
export function escapeMdCell(value) {
|
|
29
|
+
return oneLine(value).replace(/\|/g, "\\|");
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3C,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,GAAW;IACjD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IACzB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SessionSummary, SourceKind, Step } from "../types.js";
|
|
2
|
+
export declare function sourceLabel(source: SourceKind): string;
|
|
3
|
+
/** `2026-11-01T15:04:22.000Z` → `2026-11-01 15:04 UTC`. Naive timestamps omit UTC. */
|
|
4
|
+
export declare function formatWhen(value: string | undefined): string | undefined;
|
|
5
|
+
export declare function filesDetail(summary: SessionSummary, maxNames?: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* Reviewer-facing test line.
|
|
8
|
+
* A fail-then-pass pair with counts is `6 failed → 13/13 pass`.
|
|
9
|
+
* One passing run with counts is `13/13 pass`.
|
|
10
|
+
* Otherwise the last command, such as `npm test · pass`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function testsPhrase(summary: SessionSummary): string;
|
|
13
|
+
/** One passing run, and no failure, when that run published both counts. */
|
|
14
|
+
export declare function solePass(summary: SessionSummary): {
|
|
15
|
+
passed: number;
|
|
16
|
+
total: number;
|
|
17
|
+
} | undefined;
|
|
18
|
+
export declare function testsTransition(summary: SessionSummary): {
|
|
19
|
+
failed: number;
|
|
20
|
+
passed: number;
|
|
21
|
+
total: number;
|
|
22
|
+
} | undefined;
|
|
23
|
+
export declare function publicSteps(steps: Step[]): Step[];
|
|
24
|
+
export declare function asciiFlow(summary: SessionSummary): string;
|
|
25
|
+
export declare function footerLine(summary: SessionSummary): string;
|
|
26
|
+
export declare function askLine(summary: SessionSummary, max?: number): string;
|
|
27
|
+
export declare function decisionLine(summary: SessionSummary, max?: number): string;
|
|
28
|
+
export declare function stepText(step: Step): string;
|