prtape-cli 0.0.0-stage → 0.1.1
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 +140 -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 +216 -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
|
@@ -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;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { rollupTests } from "../summary.js";
|
|
2
|
+
import { firstSentence, oneLine, truncate } from "../text.js";
|
|
3
|
+
export function sourceLabel(source) {
|
|
4
|
+
return source === "claude-code" ? "Claude Code" : "Claude Code";
|
|
5
|
+
}
|
|
6
|
+
/** `2026-11-01T15:04:22.000Z` → `2026-11-01 15:04 UTC`. Naive timestamps omit UTC. */
|
|
7
|
+
export function formatWhen(value) {
|
|
8
|
+
if (!value)
|
|
9
|
+
return undefined;
|
|
10
|
+
const match = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})/.exec(value);
|
|
11
|
+
if (!match)
|
|
12
|
+
return undefined;
|
|
13
|
+
const utc = /(?:Z|[+-]\d{2}:\d{2})$/.test(value);
|
|
14
|
+
return `${match[1]} ${match[2]}${utc ? " UTC" : ""}`;
|
|
15
|
+
}
|
|
16
|
+
export function filesDetail(summary, maxNames = 3) {
|
|
17
|
+
if (summary.files.length === 0)
|
|
18
|
+
return "No files edited";
|
|
19
|
+
const names = summary.files.slice(0, maxNames).map((file) => file.path);
|
|
20
|
+
const extra = summary.files.length - names.length;
|
|
21
|
+
const listed = names.join(" · ");
|
|
22
|
+
return extra > 0 ? `${listed} · +${extra}` : listed;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Reviewer-facing test line.
|
|
26
|
+
* A fail-then-pass pair with counts is `6 failed → 13/13 pass`.
|
|
27
|
+
* One passing run with counts is `13/13 pass`.
|
|
28
|
+
* Otherwise the last command, such as `npm test · pass`.
|
|
29
|
+
*/
|
|
30
|
+
export function testsPhrase(summary) {
|
|
31
|
+
const transition = testsTransition(summary);
|
|
32
|
+
if (transition)
|
|
33
|
+
return `${transition.failed} failed → ${transition.passed}/${transition.total} pass`;
|
|
34
|
+
const only = solePass(summary);
|
|
35
|
+
if (only)
|
|
36
|
+
return `${only.passed}/${only.total} pass`;
|
|
37
|
+
const last = summary.tests.at(-1);
|
|
38
|
+
if (!last)
|
|
39
|
+
return "not run";
|
|
40
|
+
const word = last.outcome === "pass" ? "pass" : last.outcome === "fail" ? "fail" : "ran";
|
|
41
|
+
const command = oneLine(last.command);
|
|
42
|
+
return command ? `${command} · ${word}` : word;
|
|
43
|
+
}
|
|
44
|
+
/** One passing run, and no failure, when that run published both counts. */
|
|
45
|
+
export function solePass(summary) {
|
|
46
|
+
if (summary.tests.length !== 1)
|
|
47
|
+
return undefined;
|
|
48
|
+
const only = summary.tests[0];
|
|
49
|
+
if (!only || only.outcome !== "pass")
|
|
50
|
+
return undefined;
|
|
51
|
+
if (typeof only.passed !== "number" || typeof only.total !== "number")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (!Number.isInteger(only.passed) || !Number.isInteger(only.total))
|
|
54
|
+
return undefined;
|
|
55
|
+
if (only.failed !== undefined && only.failed !== 0)
|
|
56
|
+
return undefined;
|
|
57
|
+
return { passed: only.passed, total: only.total };
|
|
58
|
+
}
|
|
59
|
+
export function testsTransition(summary) {
|
|
60
|
+
const firstFail = summary.tests.find((test) => test.outcome === "fail" && typeof test.failed === "number" && test.failed > 0);
|
|
61
|
+
const last = summary.tests.at(-1);
|
|
62
|
+
if (!firstFail || last?.outcome !== "pass")
|
|
63
|
+
return undefined;
|
|
64
|
+
if (typeof last.passed !== "number" || typeof last.total !== "number")
|
|
65
|
+
return undefined;
|
|
66
|
+
if (!Number.isInteger(firstFail.failed) || !Number.isInteger(last.passed) || !Number.isInteger(last.total))
|
|
67
|
+
return undefined;
|
|
68
|
+
return { failed: firstFail.failed, passed: last.passed, total: last.total };
|
|
69
|
+
}
|
|
70
|
+
export function publicSteps(steps) {
|
|
71
|
+
return steps.filter((step) => step.kind !== "read" || Boolean(step.path));
|
|
72
|
+
}
|
|
73
|
+
export function asciiFlow(summary) {
|
|
74
|
+
const count = summary.files.length;
|
|
75
|
+
const files = count === 0 ? "no file edits" : count === 1 ? "1 file" : `${count} files`;
|
|
76
|
+
const outcome = rollupTests(summary.tests);
|
|
77
|
+
const tests = !summary.testsRun
|
|
78
|
+
? "tests not run"
|
|
79
|
+
: outcome === "pass"
|
|
80
|
+
? "tests pass"
|
|
81
|
+
: outcome === "fail"
|
|
82
|
+
? "tests fail"
|
|
83
|
+
: "tests ran";
|
|
84
|
+
return `ask ──▶ ${files} ──▶ ${tests} ──▶ ship`;
|
|
85
|
+
}
|
|
86
|
+
export function footerLine(summary) {
|
|
87
|
+
const count = summary.files.length;
|
|
88
|
+
const files = count === 0 ? "no files touched" : `${count} file${count === 1 ? "" : "s"} touched`;
|
|
89
|
+
const outcome = rollupTests(summary.tests);
|
|
90
|
+
const tests = !summary.testsRun
|
|
91
|
+
? "tests not run"
|
|
92
|
+
: outcome === "pass"
|
|
93
|
+
? "tests pass"
|
|
94
|
+
: outcome === "fail"
|
|
95
|
+
? "tests fail"
|
|
96
|
+
: "tests ran";
|
|
97
|
+
return `${files} · ${tests} · redacted before posting`;
|
|
98
|
+
}
|
|
99
|
+
export function askLine(summary, max = 80) {
|
|
100
|
+
return truncate(firstSentence(summary.title), max);
|
|
101
|
+
}
|
|
102
|
+
export function decisionLine(summary, max = 90) {
|
|
103
|
+
return truncate(oneLine(summary.decision), max);
|
|
104
|
+
}
|
|
105
|
+
export function stepText(step) {
|
|
106
|
+
switch (step.kind) {
|
|
107
|
+
case "prompt":
|
|
108
|
+
return `**Ask** — ${truncate(firstSentence(step.text), 180)}`;
|
|
109
|
+
case "read":
|
|
110
|
+
return step.path ? `**Read** — \`${step.path}\`` : "**Read**";
|
|
111
|
+
case "edit": {
|
|
112
|
+
const verb = step.action === "write" ? "Write" : step.action === "delete" ? "Delete" : "Edit";
|
|
113
|
+
return `**${verb}** — \`${step.path}\``;
|
|
114
|
+
}
|
|
115
|
+
case "test":
|
|
116
|
+
return `**Test** — \`${truncate(oneLine(step.command), 80)}\` · ${step.outcome}`;
|
|
117
|
+
case "commit":
|
|
118
|
+
return "**Commit**";
|
|
119
|
+
case "decision":
|
|
120
|
+
return `**Decision** — ${truncate(oneLine(step.text), 500)}`;
|
|
121
|
+
default: {
|
|
122
|
+
const never = step;
|
|
123
|
+
return never;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/timeline/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SessionSummary } from "../types.js";
|
|
2
|
+
import type { RedactOptions } from "../redact.js";
|
|
3
|
+
export interface CommentImage {
|
|
4
|
+
url: string;
|
|
5
|
+
isPrivate: boolean;
|
|
6
|
+
/** Dry-run image name. The absolute path stays on stderr. */
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Pull-request comment body.
|
|
11
|
+
*
|
|
12
|
+
* The happy path leads with the hosted PNG (`github.com/.../raw/<sha>/timeline.png`),
|
|
13
|
+
* then the one-line decision and a short table. Mermaid and ASCII are the
|
|
14
|
+
* fallback when the orphan ref was not pushed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function renderComment(input: SessionSummary, options?: RedactOptions, image?: CommentImage, demo?: boolean): string;
|
|
17
|
+
export declare function terminalSummary(summary: SessionSummary): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { VERSION } from "../version.js";
|
|
2
|
+
import { presentSummary } from "../display.js";
|
|
3
|
+
import { escapeMdCell, oneLine } from "../text.js";
|
|
4
|
+
import { asciiFlow, decisionLine, formatWhen, publicSteps, sourceLabel, stepText, testsPhrase, } from "./format.js";
|
|
5
|
+
import { rollupTests } from "../summary.js";
|
|
6
|
+
/**
|
|
7
|
+
* Pull-request comment body.
|
|
8
|
+
*
|
|
9
|
+
* The happy path leads with the hosted PNG (`github.com/.../raw/<sha>/timeline.png`),
|
|
10
|
+
* then the one-line decision and a short table. Mermaid and ASCII are the
|
|
11
|
+
* fallback when the orphan ref was not pushed.
|
|
12
|
+
*/
|
|
13
|
+
export function renderComment(input, options = {}, image, demo = false) {
|
|
14
|
+
const summary = presentSummary(input, options).summary;
|
|
15
|
+
const decision = decisionLine(summary, 200);
|
|
16
|
+
const when = formatWhen(summary.startedAt);
|
|
17
|
+
const sourceBits = [
|
|
18
|
+
sourceLabel(summary.source),
|
|
19
|
+
summary.branch ? `\`${summary.branch.replace(/`/g, "")}\`` : undefined,
|
|
20
|
+
when,
|
|
21
|
+
].filter((bit) => Boolean(bit));
|
|
22
|
+
const fileNames = summary.files.slice(0, 8).map((file) => `\`${file.path.replace(/`/g, "")}\``);
|
|
23
|
+
const extraFiles = summary.files.length - fileNames.length;
|
|
24
|
+
const files = fileNames.length === 0
|
|
25
|
+
? "none"
|
|
26
|
+
: `${fileNames.join(" · ")}${extraFiles > 0 ? ` · +${extraFiles}` : ""}`;
|
|
27
|
+
const table = [
|
|
28
|
+
"| | |",
|
|
29
|
+
"| --- | --- |",
|
|
30
|
+
`| Source | ${escapeMdCell(sourceBits.join(" · "))} |`,
|
|
31
|
+
`| Files touched | ${escapeMdCell(files)} |`,
|
|
32
|
+
`| Tests | ${escapeMdCell(testsCell(summary))} |`,
|
|
33
|
+
];
|
|
34
|
+
const footer = `_Redacted locally by PRtape ${VERSION}. The raw session was not posted._`;
|
|
35
|
+
const marker = "<!-- prtape -->";
|
|
36
|
+
const lines = image
|
|
37
|
+
? [
|
|
38
|
+
``,
|
|
39
|
+
"",
|
|
40
|
+
marker,
|
|
41
|
+
"",
|
|
42
|
+
`**${escapeMdCell(decision)}**`,
|
|
43
|
+
"",
|
|
44
|
+
...(demo ? ["Demo project", ""] : []),
|
|
45
|
+
...table,
|
|
46
|
+
"",
|
|
47
|
+
image.dryRun ? dryRunNote() : visibilityNote(image.isPrivate),
|
|
48
|
+
"",
|
|
49
|
+
footer,
|
|
50
|
+
"",
|
|
51
|
+
]
|
|
52
|
+
: [
|
|
53
|
+
"### PRtape",
|
|
54
|
+
"",
|
|
55
|
+
marker,
|
|
56
|
+
"",
|
|
57
|
+
`**${escapeMdCell(decision)}**`,
|
|
58
|
+
"",
|
|
59
|
+
...(demo ? ["Demo project", ""] : []),
|
|
60
|
+
"```text",
|
|
61
|
+
asciiFlow(summary),
|
|
62
|
+
"```",
|
|
63
|
+
"",
|
|
64
|
+
...table,
|
|
65
|
+
"",
|
|
66
|
+
"```mermaid",
|
|
67
|
+
mermaid(summary),
|
|
68
|
+
"```",
|
|
69
|
+
"",
|
|
70
|
+
"<details>",
|
|
71
|
+
"<summary>Steps</summary>",
|
|
72
|
+
"",
|
|
73
|
+
...publicSteps(summary.steps).map((step, index) => `${index + 1}. ${stepText(step)}`),
|
|
74
|
+
"",
|
|
75
|
+
"</details>",
|
|
76
|
+
"",
|
|
77
|
+
`_Redacted locally by PRtape ${VERSION}. The raw session was not posted. This is the text timeline (ASCII + mermaid). GitHub strips inline SVG._`,
|
|
78
|
+
"",
|
|
79
|
+
];
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
function dryRunNote() {
|
|
83
|
+
return [
|
|
84
|
+
"_Dry run. Nothing was uploaded and no comment was posted. The local PNG path is printed on stderr, not in this comment._",
|
|
85
|
+
"_A real `prtape pr` pushes the PNG to the orphan ref `refs/prtape/<head>` and the comment leads with `https://github.com/{owner}/{repo}/raw/{sha}/timeline.png`._",
|
|
86
|
+
"_On a public repo, logged-out viewers see that image. On a private repo they may not; collaborators who can open the repo usually see it._",
|
|
87
|
+
].join(" ");
|
|
88
|
+
}
|
|
89
|
+
function visibilityNote(isPrivate) {
|
|
90
|
+
const which = isPrivate ? "This repo is private." : "This repo is public.";
|
|
91
|
+
return [
|
|
92
|
+
which,
|
|
93
|
+
"On a public repo, anonymous viewers see the PNG at `github.com/.../raw/<sha>/...`.",
|
|
94
|
+
"On a private repo, logged-out users, email notifications, and some mobile clients may show a broken image.",
|
|
95
|
+
"Collaborators who can open the repo usually see it.",
|
|
96
|
+
].join(" ");
|
|
97
|
+
}
|
|
98
|
+
function testsCell(summary) {
|
|
99
|
+
const phrase = testsPhrase(summary);
|
|
100
|
+
if (!summary.testsRun)
|
|
101
|
+
return phrase;
|
|
102
|
+
const parts = phrase.split(" · ");
|
|
103
|
+
if (parts.length < 3)
|
|
104
|
+
return phrase;
|
|
105
|
+
const command = parts.slice(2).join(" · ").replace(/`/g, "");
|
|
106
|
+
return `${parts[0]} · ${parts[1]} · \`${command}\``;
|
|
107
|
+
}
|
|
108
|
+
function mermaid(summary) {
|
|
109
|
+
const count = summary.files.length;
|
|
110
|
+
const files = count === 0 ? "No edits" : `${count} file${count === 1 ? "" : "s"}`;
|
|
111
|
+
const outcome = rollupTests(summary.tests);
|
|
112
|
+
const tests = !summary.testsRun
|
|
113
|
+
? "Tests not run"
|
|
114
|
+
: outcome === "pass"
|
|
115
|
+
? "Tests pass"
|
|
116
|
+
: outcome === "fail"
|
|
117
|
+
? "Tests fail"
|
|
118
|
+
: "Tests ran";
|
|
119
|
+
return [
|
|
120
|
+
"flowchart LR",
|
|
121
|
+
" stepAsk[Ask] --> stepFiles[" + files + "]",
|
|
122
|
+
" stepFiles --> stepTests[" + tests + "]",
|
|
123
|
+
" stepTests --> stepShip[Ship]",
|
|
124
|
+
].join("\n");
|
|
125
|
+
}
|
|
126
|
+
export function terminalSummary(summary) {
|
|
127
|
+
return `${oneLine(summary.decision)}\n${asciiFlow(summary)}`;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=md.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md.js","sourceRoot":"","sources":["../../src/timeline/md.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionSummary } from "../types.js";
|
|
2
|
+
import type { RedactOptions } from "../redact.js";
|
|
3
|
+
/**
|
|
4
|
+
* 960×480 PR timeline. Six-segment horizontal track, then FILES / TESTS /
|
|
5
|
+
* KEY DECISION. Secrets are omitted, not masked in the body or the footer.
|
|
6
|
+
*/
|
|
7
|
+
export declare function renderSvg(input: SessionSummary, options?: RedactOptions, demo?: boolean): string;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { presentSummary } from "../display.js";
|
|
2
|
+
import { escapeXml } from "../text.js";
|
|
3
|
+
import { rollupTests } from "../summary.js";
|
|
4
|
+
import { solePass, sourceLabel, testsPhrase, testsTransition } from "./format.js";
|
|
5
|
+
import { textWidth, wrapText } from "./wrap.js";
|
|
6
|
+
const WIDTH = 960;
|
|
7
|
+
const HEIGHT = 480;
|
|
8
|
+
const MONO = "JetBrains Mono, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace";
|
|
9
|
+
const SANS = "Inter, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', sans-serif";
|
|
10
|
+
const BG = "#0B0F14";
|
|
11
|
+
const CARD = "#101820";
|
|
12
|
+
const ROW = "#17212C";
|
|
13
|
+
const TRACK = "#1B2430";
|
|
14
|
+
const INK = "#E6EDF3";
|
|
15
|
+
const MUTED = "#7D8A99";
|
|
16
|
+
const ACCENT = "#39FF88";
|
|
17
|
+
const ACCENT2 = "#22D3EE";
|
|
18
|
+
const ADD = "#3DDC84";
|
|
19
|
+
const REMOVE = "#FF5C7A";
|
|
20
|
+
/** Same brightness as the other phase colors. #33445A sat in the background and looked unused. */
|
|
21
|
+
const READ = "#7A8FB0";
|
|
22
|
+
const CHIP = "#0F2A1D";
|
|
23
|
+
const BEAT_COLOR = {
|
|
24
|
+
read: READ,
|
|
25
|
+
edit: ACCENT2,
|
|
26
|
+
fail: REMOVE,
|
|
27
|
+
pass: ADD,
|
|
28
|
+
commit: ACCENT,
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 960×480 PR timeline. Six-segment horizontal track, then FILES / TESTS /
|
|
32
|
+
* KEY DECISION. Secrets are omitted, not masked in the body or the footer.
|
|
33
|
+
*/
|
|
34
|
+
export function renderSvg(input, options = {}, demo = false) {
|
|
35
|
+
const { summary, maskedCount } = presentSummary(input, options);
|
|
36
|
+
const beats = observedBeats(summary);
|
|
37
|
+
const source = sourceLabel(summary.source);
|
|
38
|
+
const parts = [];
|
|
39
|
+
parts.push(`<?xml version="1.0" encoding="UTF-8"?>`);
|
|
40
|
+
parts.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}" role="img">`);
|
|
41
|
+
parts.push(` <title>${escapeXml(`PRtape timeline · ${source}`)}</title>`);
|
|
42
|
+
parts.push(` <desc>${escapeXml(summary.decision)}</desc>`);
|
|
43
|
+
parts.push(` <defs>`);
|
|
44
|
+
parts.push(` <linearGradient id="reel" x1="0" y1="0" x2="1" y2="1">`);
|
|
45
|
+
parts.push(` <stop offset="0" stop-color="${ACCENT}"/>`);
|
|
46
|
+
parts.push(` <stop offset="1" stop-color="${ACCENT2}"/>`);
|
|
47
|
+
parts.push(` </linearGradient>`);
|
|
48
|
+
parts.push(` </defs>`);
|
|
49
|
+
parts.push(` <rect width="${WIDTH}" height="${HEIGHT}" rx="18" fill="${BG}"/>`);
|
|
50
|
+
parts.push(` <rect x="16" y="16" width="928" height="448" rx="14" fill="${CARD}" stroke="${ACCENT}" stroke-width="1.5"/>`);
|
|
51
|
+
parts.push(reelMark(28, 26, 48));
|
|
52
|
+
parts.push(wordmark(86, 52));
|
|
53
|
+
parts.push(chips(source, maskedCount, demo));
|
|
54
|
+
parts.push(track(beats));
|
|
55
|
+
parts.push(filesRow(summary));
|
|
56
|
+
parts.push(testsRow(summary));
|
|
57
|
+
parts.push(decisionRow(summary.decision));
|
|
58
|
+
parts.push(footer());
|
|
59
|
+
parts.push(`</svg>`);
|
|
60
|
+
parts.push("");
|
|
61
|
+
return parts.join("\n");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Headline phases. The step list keeps every event. The track drops setup
|
|
65
|
+
* reads before the first test, verification reads after the last edit, and
|
|
66
|
+
* pathless reads such as a bare `git diff`.
|
|
67
|
+
*/
|
|
68
|
+
function observedBeats(summary) {
|
|
69
|
+
const beats = [];
|
|
70
|
+
for (const label of phaseLabels(summary.events)) {
|
|
71
|
+
if (beats.at(-1)?.label === label)
|
|
72
|
+
continue;
|
|
73
|
+
const color = BEAT_COLOR[label];
|
|
74
|
+
if (!color)
|
|
75
|
+
continue;
|
|
76
|
+
beats.push({ label, color });
|
|
77
|
+
}
|
|
78
|
+
return beats;
|
|
79
|
+
}
|
|
80
|
+
function phaseLabels(events) {
|
|
81
|
+
const hasTest = events.some((event) => event.kind === "test" && (event.outcome === "fail" || event.outcome === "pass"));
|
|
82
|
+
let firstTest = -1;
|
|
83
|
+
let lastEdit = -1;
|
|
84
|
+
events.forEach((event, index) => {
|
|
85
|
+
if (firstTest < 0 && event.kind === "test" && (event.outcome === "fail" || event.outcome === "pass"))
|
|
86
|
+
firstTest = index;
|
|
87
|
+
if (event.kind === "edit")
|
|
88
|
+
lastEdit = index;
|
|
89
|
+
});
|
|
90
|
+
const labels = [];
|
|
91
|
+
events.forEach((event, index) => {
|
|
92
|
+
if (event.kind === "read") {
|
|
93
|
+
if (!event.path)
|
|
94
|
+
return;
|
|
95
|
+
if (hasTest && index < firstTest)
|
|
96
|
+
return;
|
|
97
|
+
if (lastEdit >= 0 && index > lastEdit)
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const label = beatLabel(event);
|
|
101
|
+
if (label)
|
|
102
|
+
labels.push(label);
|
|
103
|
+
});
|
|
104
|
+
return labels;
|
|
105
|
+
}
|
|
106
|
+
function beatLabel(event) {
|
|
107
|
+
switch (event.kind) {
|
|
108
|
+
case "read":
|
|
109
|
+
return "read";
|
|
110
|
+
case "edit":
|
|
111
|
+
return "edit";
|
|
112
|
+
case "test":
|
|
113
|
+
if (event.outcome === "fail")
|
|
114
|
+
return "fail";
|
|
115
|
+
if (event.outcome === "pass")
|
|
116
|
+
return "pass";
|
|
117
|
+
return undefined;
|
|
118
|
+
case "commit":
|
|
119
|
+
return "commit";
|
|
120
|
+
default: {
|
|
121
|
+
const never = event;
|
|
122
|
+
return never;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Reel portion of logo-mark.svg. The prompt caret is left out here: at header
|
|
128
|
+
* size it reads as a terminal `>_` glyph. The arc, tape, and hubs stay.
|
|
129
|
+
*/
|
|
130
|
+
function reelMark(x, y, size) {
|
|
131
|
+
const scale = (size / 128).toFixed(4);
|
|
132
|
+
return [
|
|
133
|
+
` <g transform="translate(${x} ${y}) scale(${scale})" aria-label="PRtape reel">`,
|
|
134
|
+
` <rect width="128" height="128" rx="28" fill="${BG}"/>`,
|
|
135
|
+
` <g transform="translate(0 6)">`,
|
|
136
|
+
` <path d="M112 91 H52 A33 33 0 1 1 52 25 A33 33 0 1 1 52 91" fill="none" stroke="url(#reel)" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
137
|
+
` <rect x="62" y="68" width="14" height="6" rx="1.5" fill="${ACCENT}"/>`,
|
|
138
|
+
` <circle cx="82" cy="91" r="5.5" fill="${ACCENT}" stroke="${BG}" stroke-width="3"/>`,
|
|
139
|
+
` <circle cx="102" cy="91" r="5.5" fill="${REMOVE}" stroke="${BG}" stroke-width="3"/>`,
|
|
140
|
+
` </g>`,
|
|
141
|
+
` </g>`,
|
|
142
|
+
].join("\n");
|
|
143
|
+
}
|
|
144
|
+
function wordmark(x, y) {
|
|
145
|
+
return [
|
|
146
|
+
` <text x="${x}" y="${y}" font-family="${MONO}" font-size="20" font-weight="800">`,
|
|
147
|
+
` <tspan fill="${INK}">PR</tspan><tspan fill="${ACCENT}">tape</tspan>`,
|
|
148
|
+
` </text>`,
|
|
149
|
+
].join("\n");
|
|
150
|
+
}
|
|
151
|
+
function chips(source, maskedCount, demo) {
|
|
152
|
+
const masked = maskedCount > 0 ? `redacted ×${maskedCount}` : "";
|
|
153
|
+
const maskedW = masked ? Math.max(108, masked.length * 8 + 22) : 0;
|
|
154
|
+
const sourceW = Math.max(108, source.length * 8.2 + 24);
|
|
155
|
+
const demoLabel = demo ? "Demo project" : "";
|
|
156
|
+
const demoW = demoLabel ? Math.max(108, demoLabel.length * 8.2 + 24) : 0;
|
|
157
|
+
const right = 928;
|
|
158
|
+
const maskedX = right - maskedW;
|
|
159
|
+
const sourceX = (masked ? maskedX : right) - 8 - sourceW;
|
|
160
|
+
const demoX = sourceX - (demoW ? 8 + demoW : 0);
|
|
161
|
+
const chunks = [
|
|
162
|
+
` <rect x="${sourceX}" y="34" width="${sourceW}" height="28" rx="14" fill="${TRACK}" stroke="#263241"/>`,
|
|
163
|
+
text(sourceX + sourceW / 2, 53, source, { size: 13, fill: INK, family: "mono", anchor: "middle" }),
|
|
164
|
+
];
|
|
165
|
+
if (demoLabel) {
|
|
166
|
+
chunks.push(` <rect x="${demoX}" y="34" width="${demoW.toFixed(1)}" height="28" rx="14" fill="none" stroke="${MUTED}"/>`);
|
|
167
|
+
chunks.push(text(demoX + demoW / 2, 53, demoLabel, { size: 13, fill: MUTED, family: "mono", anchor: "middle" }));
|
|
168
|
+
}
|
|
169
|
+
if (masked) {
|
|
170
|
+
chunks.push(` <rect x="${maskedX}" y="34" width="${maskedW}" height="28" rx="14" fill="${CHIP}" stroke="${ACCENT}"/>`);
|
|
171
|
+
chunks.push(text(maskedX + maskedW / 2, 53, masked, { size: 13, fill: ACCENT, family: "mono", weight: 700, anchor: "middle" }));
|
|
172
|
+
}
|
|
173
|
+
return chunks.join("\n");
|
|
174
|
+
}
|
|
175
|
+
function track(beats) {
|
|
176
|
+
const x0 = 36;
|
|
177
|
+
const y = 78;
|
|
178
|
+
const width = 888;
|
|
179
|
+
const gap = 6;
|
|
180
|
+
if (beats.length === 0) {
|
|
181
|
+
return ` <rect x="${x0}" y="${y}" width="${width}" height="22" rx="6" fill="${TRACK}"/>`;
|
|
182
|
+
}
|
|
183
|
+
const blockW = (width - gap * (beats.length - 1)) / beats.length;
|
|
184
|
+
const chunks = [` <rect x="${x0}" y="${y}" width="${width}" height="22" rx="6" fill="${TRACK}"/>`];
|
|
185
|
+
beats.forEach((beat, index) => {
|
|
186
|
+
const x = x0 + index * (blockW + gap);
|
|
187
|
+
chunks.push(` <rect x="${x.toFixed(1)}" y="${y}" width="${blockW.toFixed(1)}" height="22" rx="4" fill="${beat.color}"/>`);
|
|
188
|
+
chunks.push(text(x + blockW / 2, y + 40, beat.label, {
|
|
189
|
+
size: 13,
|
|
190
|
+
fill: beat.color,
|
|
191
|
+
family: "mono",
|
|
192
|
+
anchor: "middle",
|
|
193
|
+
}));
|
|
194
|
+
});
|
|
195
|
+
return chunks.join("\n");
|
|
196
|
+
}
|
|
197
|
+
function filesRow(summary) {
|
|
198
|
+
const y = 132;
|
|
199
|
+
const names = summary.files.slice(0, 4).map((file) => file.path);
|
|
200
|
+
const extra = summary.files.length - names.length;
|
|
201
|
+
const chunks = [row(y, 86), heading(y, "FILES TOUCHED", String(summary.files.length))];
|
|
202
|
+
let x = 48;
|
|
203
|
+
if (names.length === 0) {
|
|
204
|
+
chunks.push(text(48, y + 58, "No files edited", { size: 16, fill: MUTED, family: "sans" }));
|
|
205
|
+
return chunks.join("\n");
|
|
206
|
+
}
|
|
207
|
+
for (const name of names) {
|
|
208
|
+
const w = name.length * 8.6 + 28;
|
|
209
|
+
chunks.push(` <rect x="${x}" y="${y + 40}" width="${w.toFixed(1)}" height="28" rx="7" fill="${BG}" stroke="#263241"/>`);
|
|
210
|
+
chunks.push(text(x + 14, y + 59, name, { size: 14, fill: INK, family: "mono" }));
|
|
211
|
+
x += w + 18;
|
|
212
|
+
}
|
|
213
|
+
if (extra > 0)
|
|
214
|
+
chunks.push(text(x, y + 59, `+${extra}`, { size: 14, fill: MUTED, family: "mono" }));
|
|
215
|
+
return chunks.join("\n");
|
|
216
|
+
}
|
|
217
|
+
function testsRow(summary) {
|
|
218
|
+
const y = 226;
|
|
219
|
+
const transition = testsTransition(summary);
|
|
220
|
+
const only = solePass(summary);
|
|
221
|
+
const outcome = rollupTests(summary.tests);
|
|
222
|
+
const color = outcome === "pass" ? ADD : outcome === "fail" ? REMOVE : MUTED;
|
|
223
|
+
const phrase = transition
|
|
224
|
+
? ` <text x="48.0" y="${y + 56}" font-family="${MONO}" font-size="18"><tspan fill="${REMOVE}">${transition.failed} failed</tspan><tspan fill="${MUTED}"> → </tspan><tspan fill="${ADD}">${transition.passed}/${transition.total} pass</tspan></text>`
|
|
225
|
+
: text(48, y + 56, only ? `${only.passed}/${only.total} pass` : testsPhrase(summary), {
|
|
226
|
+
size: 18,
|
|
227
|
+
fill: only ? ADD : color,
|
|
228
|
+
family: "mono",
|
|
229
|
+
});
|
|
230
|
+
return [row(y, 78), heading(y, "TESTS RAN", ""), phrase].join("\n");
|
|
231
|
+
}
|
|
232
|
+
function decisionRow(decision) {
|
|
233
|
+
const y = 312;
|
|
234
|
+
const maxWidth = 848;
|
|
235
|
+
const single = wrapText(decision, maxWidth, 20, 1);
|
|
236
|
+
const oneLineFits = single.length === 1 && single[0] !== undefined && !single[0].endsWith("…") && textWidth(single[0], 20) <= maxWidth;
|
|
237
|
+
const lines = oneLineFits ? single : wrapText(decision, maxWidth, 16, 2);
|
|
238
|
+
const size = oneLineFits ? 20 : 16;
|
|
239
|
+
const start = oneLineFits ? y + 58 : y + 46;
|
|
240
|
+
const step = 22;
|
|
241
|
+
const body = lines.map((line, index) => text(48, start + index * step, line, { size, fill: INK, family: "sans", weight: 700 }));
|
|
242
|
+
return [row(y, 86), heading(y, "KEY DECISION", ""), ...body].join("\n");
|
|
243
|
+
}
|
|
244
|
+
function footer() {
|
|
245
|
+
return text(36, 436, "redacted before posting · raw session never uploaded", { size: 13, fill: MUTED, family: "mono" });
|
|
246
|
+
}
|
|
247
|
+
function row(y, height) {
|
|
248
|
+
return ` <rect x="32" y="${y}" width="896" height="${height}" rx="10" fill="${ROW}"/>`;
|
|
249
|
+
}
|
|
250
|
+
function heading(y, label, count) {
|
|
251
|
+
const suffix = count ? ` · ${count}` : "";
|
|
252
|
+
return text(48, y + 24, `${label}${suffix}`, { size: 13, fill: MUTED, family: "mono", weight: 700 });
|
|
253
|
+
}
|
|
254
|
+
function text(x, y, value, style) {
|
|
255
|
+
const family = style.family === "mono" ? MONO : SANS;
|
|
256
|
+
const weight = style.weight ? ` font-weight="${style.weight}"` : "";
|
|
257
|
+
const anchor = style.anchor && style.anchor !== "start" ? ` text-anchor="${style.anchor}"` : "";
|
|
258
|
+
return ` <text x="${x.toFixed(1)}" y="${y}" fill="${style.fill}" font-family="${family}" font-size="${style.size}"${weight}${anchor}>${escapeXml(value)}</text>`;
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=svg.js.map
|