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
package/dist/redact.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { withSteps } from "./summary.js";
|
|
2
|
+
/**
|
|
3
|
+
* Replacement tokens. They are stable on purpose so a second pass is a no-op
|
|
4
|
+
* and tests can assert on them.
|
|
5
|
+
*/
|
|
6
|
+
export const REDACTED_API_KEY = "[REDACTED_API_KEY]";
|
|
7
|
+
export const REDACTED_GITHUB_TOKEN = "[REDACTED_GITHUB_TOKEN]";
|
|
8
|
+
export const REDACTED_AWS_KEY = "[REDACTED_AWS_KEY]";
|
|
9
|
+
export const REDACTED_IP = "[REDACTED_IP]";
|
|
10
|
+
export const REDACTED_HOST = "[REDACTED_HOST]";
|
|
11
|
+
export const REDACTED_EMAIL = "[REDACTED_EMAIL]";
|
|
12
|
+
/**
|
|
13
|
+
* Default redaction. Order matters: `sk-ant-` before generic `sk-`.
|
|
14
|
+
*
|
|
15
|
+
* Patterns are intentionally prefix-based, not entropy-based. A novel secret
|
|
16
|
+
* that does not match these shapes is not caught. Emails are off unless
|
|
17
|
+
* `options.emails` is set.
|
|
18
|
+
*/
|
|
19
|
+
const SECRET_RULES = [
|
|
20
|
+
{
|
|
21
|
+
pattern: /sk-ant-[A-Za-z0-9_-]{8,}/g,
|
|
22
|
+
replace: REDACTED_API_KEY,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pattern: /(?<![A-Za-z0-9_-])sk-[A-Za-z0-9_-]{8,}/g,
|
|
26
|
+
replace: REDACTED_API_KEY,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
pattern: /gh[pousr]_[A-Za-z0-9]{20,}/g,
|
|
30
|
+
replace: REDACTED_GITHUB_TOKEN,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
pattern: /(?<![A-Z0-9])(?:AKIA|ASIA|AIDA|AROA)[A-Z0-9]{16}(?![A-Z0-9])/g,
|
|
34
|
+
replace: REDACTED_AWS_KEY,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
pattern: /(aws_secret_access_key|AWS_SECRET_ACCESS_KEY)(\s*[=:]\s*)(["']?)[^\s"'\\]+/gi,
|
|
38
|
+
replace: `$1$2$3${REDACTED_AWS_KEY}`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
// RFC1918 plus link-local. Loopback is left alone.
|
|
42
|
+
pattern: /\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}|169\.254(?:\.\d{1,3}){2})\b/g,
|
|
43
|
+
replace: REDACTED_IP,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
// Hostnames that end in .internal or .local. `localhost` has no dot, so it stays.
|
|
47
|
+
pattern: /\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+(?:internal|local)(?![A-Za-z0-9.-])/g,
|
|
48
|
+
replace: REDACTED_HOST,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
const EMAIL_RULE = {
|
|
52
|
+
pattern: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
|
|
53
|
+
replace: REDACTED_EMAIL,
|
|
54
|
+
};
|
|
55
|
+
export function redact(value, options = {}) {
|
|
56
|
+
let out = value;
|
|
57
|
+
for (const rule of SECRET_RULES) {
|
|
58
|
+
out = out.replace(rule.pattern, rule.replace);
|
|
59
|
+
}
|
|
60
|
+
if (options.emails) {
|
|
61
|
+
out = out.replace(EMAIL_RULE.pattern, EMAIL_RULE.replace);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function redactEvent(event, options) {
|
|
66
|
+
if (event.kind === "read")
|
|
67
|
+
return { kind: "read", path: event.path ? redact(event.path, options) : undefined };
|
|
68
|
+
if (event.kind === "edit")
|
|
69
|
+
return { kind: "edit", path: redact(event.path, options), action: event.action };
|
|
70
|
+
if (event.kind === "test")
|
|
71
|
+
return { ...event, command: redact(event.command, options) };
|
|
72
|
+
return event;
|
|
73
|
+
}
|
|
74
|
+
function redactOptional(value, options) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
return redact(value, options);
|
|
78
|
+
}
|
|
79
|
+
export function redactSession(summary, options = {}) {
|
|
80
|
+
const draft = {
|
|
81
|
+
source: summary.source,
|
|
82
|
+
sessionId: redact(summary.sessionId, options),
|
|
83
|
+
startedAt: redactOptional(summary.startedAt, options),
|
|
84
|
+
model: redactOptional(summary.model, options),
|
|
85
|
+
branch: redactOptional(summary.branch, options),
|
|
86
|
+
cwd: redactOptional(summary.cwd, options),
|
|
87
|
+
title: redact(summary.title, options),
|
|
88
|
+
decision: redact(summary.decision, options),
|
|
89
|
+
files: summary.files.map((file) => ({
|
|
90
|
+
path: redact(file.path, options),
|
|
91
|
+
action: file.action,
|
|
92
|
+
})),
|
|
93
|
+
tests: summary.tests.map((test) => ({
|
|
94
|
+
command: redact(test.command, options),
|
|
95
|
+
outcome: test.outcome,
|
|
96
|
+
})),
|
|
97
|
+
events: summary.events?.map((event) => redactEvent(event, options)),
|
|
98
|
+
};
|
|
99
|
+
return withSteps(draft);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type SourceChoice } from "./discover.js";
|
|
2
|
+
import { type CommandRunner } from "./gh.js";
|
|
3
|
+
import { type HostResult } from "./orphan.js";
|
|
4
|
+
import type { SessionSummary } from "./types.js";
|
|
5
|
+
export type ImageHost = (input: {
|
|
6
|
+
cwd: string;
|
|
7
|
+
png: Buffer;
|
|
8
|
+
}) => Promise<HostResult>;
|
|
9
|
+
export interface RunOptions {
|
|
10
|
+
cwd: string;
|
|
11
|
+
sessionPath?: string;
|
|
12
|
+
source: SourceChoice;
|
|
13
|
+
claudeHome?: string;
|
|
14
|
+
dryRun: boolean;
|
|
15
|
+
redactEmails: boolean;
|
|
16
|
+
/** Comment on this pull request instead of the one for the current branch. */
|
|
17
|
+
prNumber?: number;
|
|
18
|
+
/** Show the Demo project tag. Off unless `--demo` or `--label demo` is set. */
|
|
19
|
+
demo?: boolean;
|
|
20
|
+
tmpDir?: string;
|
|
21
|
+
runner?: CommandRunner;
|
|
22
|
+
recentFiles?: string[];
|
|
23
|
+
/** Test hook. Production uses `hostTimelinePng`. */
|
|
24
|
+
hostImage?: ImageHost;
|
|
25
|
+
}
|
|
26
|
+
export interface RunResult {
|
|
27
|
+
summary: SessionSummary;
|
|
28
|
+
comment: string;
|
|
29
|
+
svg: string;
|
|
30
|
+
svgPath: string;
|
|
31
|
+
pngPath?: string;
|
|
32
|
+
posted: boolean;
|
|
33
|
+
imageHosted: boolean;
|
|
34
|
+
imageUrl?: string;
|
|
35
|
+
note: string;
|
|
36
|
+
sessionNote?: string;
|
|
37
|
+
prNumber?: number;
|
|
38
|
+
prUrl?: string;
|
|
39
|
+
origin: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function toPublicJson(summary: SessionSummary, image?: {
|
|
42
|
+
imageHosted: boolean;
|
|
43
|
+
imageUrl?: string;
|
|
44
|
+
}): unknown;
|
|
45
|
+
export declare function runPr(options: RunOptions): Promise<RunResult>;
|
|
46
|
+
export declare function formatPosted(result: RunResult): string;
|
|
47
|
+
export declare function writeTimelineFile(contents: string | Buffer, cwd: string, sessionId: string, tmpDir: string, extension: "svg" | "png"): string;
|
|
48
|
+
export declare function assertNotInsideProject(target: string, cwd: string): void;
|
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
|