@jwilger/pi-development-system 0.8.8 → 0.8.10
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/package.json
CHANGED
|
@@ -18,6 +18,8 @@ type Options = {
|
|
|
18
18
|
trailers: string[];
|
|
19
19
|
file: string | undefined;
|
|
20
20
|
opaque: boolean;
|
|
21
|
+
/** Heredoc markers found inside opaque `-m` values (`$(cat <<EOF …)`). */
|
|
22
|
+
markers: number[];
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
type Parsed = {
|
|
@@ -68,12 +70,21 @@ const OPAQUE_VALUE = /^\s*(?:\$\(|`)|^\$\{?\w+\}?$/;
|
|
|
68
70
|
function record(options: Options, parsed: Parsed): void {
|
|
69
71
|
if (parsed.kind === "file") options.file = parsed.value;
|
|
70
72
|
else if (parsed.kind === "trailer") options.trailers.push(parsed.value ?? "");
|
|
71
|
-
else if (parsed.value === undefined || OPAQUE_VALUE.test(parsed.value))
|
|
72
|
-
|
|
73
|
+
else if (parsed.value === undefined || OPAQUE_VALUE.test(parsed.value)) {
|
|
74
|
+
options.opaque = true;
|
|
75
|
+
for (const hit of (parsed.value ?? "").matchAll(/#HD(\d+)/g))
|
|
76
|
+
options.markers.push(Number(hit[1]));
|
|
77
|
+
} else options.messages.push(parsed.value);
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
function readOptions(args: readonly string[]): Options {
|
|
76
|
-
const options: Options = {
|
|
81
|
+
const options: Options = {
|
|
82
|
+
messages: [],
|
|
83
|
+
trailers: [],
|
|
84
|
+
file: undefined,
|
|
85
|
+
opaque: false,
|
|
86
|
+
markers: [],
|
|
87
|
+
};
|
|
77
88
|
for (let i = 0; i < args.length; ) {
|
|
78
89
|
const parsed = parseArg(args, i);
|
|
79
90
|
if (parsed !== undefined) record(options, parsed);
|
|
@@ -100,7 +111,11 @@ const heredocFrom = (lines: readonly string[], line: number, bodies: readonly st
|
|
|
100
111
|
return undefined;
|
|
101
112
|
};
|
|
102
113
|
|
|
103
|
-
function extractOne(
|
|
114
|
+
function extractOne(
|
|
115
|
+
args: readonly string[],
|
|
116
|
+
heredoc: string | undefined,
|
|
117
|
+
bodies: readonly string[],
|
|
118
|
+
): CommitExtraction {
|
|
104
119
|
const options = readOptions(args);
|
|
105
120
|
if (options.file !== undefined && STDIN_PATHS.has(options.file)) {
|
|
106
121
|
return heredoc === undefined
|
|
@@ -110,8 +125,12 @@ function extractOne(args: readonly string[], heredoc: string | undefined): Commi
|
|
|
110
125
|
if (options.file !== undefined) return { kind: "file", path: options.file };
|
|
111
126
|
const parts = [...options.messages];
|
|
112
127
|
if (options.opaque) {
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
// The value's own heredoc, not whichever heredoc opens first on a shared line.
|
|
129
|
+
const own = options.markers.flatMap((i) => bodies[i] ?? []);
|
|
130
|
+
const fallback = heredoc === undefined ? [] : [heredoc];
|
|
131
|
+
const bodiesOfValue = own.length > 0 ? own : fallback;
|
|
132
|
+
if (bodiesOfValue.length === 0) return unknown(options.trailers, true);
|
|
133
|
+
parts.push(...bodiesOfValue);
|
|
115
134
|
}
|
|
116
135
|
if (parts.length === 0) return unknown(options.trailers);
|
|
117
136
|
const trailers = options.trailers.length === 0 ? "" : `\n\n${options.trailers.join("\n")}`;
|
|
@@ -124,7 +143,7 @@ export function extractCommits(command: string): CommitExtraction[] {
|
|
|
124
143
|
const resolution = resolveGit(rest);
|
|
125
144
|
const lines = splitLines(rest);
|
|
126
145
|
const found = resolution.invocations.flatMap((g) =>
|
|
127
|
-
g.sub === "commit" ? [extractOne(g.args, heredocFrom(lines, g.line, bodies))] : [],
|
|
146
|
+
g.sub === "commit" ? [extractOne(g.args, heredocFrom(lines, g.line, bodies), bodies)] : [],
|
|
128
147
|
);
|
|
129
148
|
const hidden = opaqueMentions(resolution, "commit");
|
|
130
149
|
return hidden ? [...found, unknown()] : found;
|
|
@@ -26,7 +26,6 @@ export type GitInvocation = {
|
|
|
26
26
|
|
|
27
27
|
export type GitResolution = {
|
|
28
28
|
readonly invocations: readonly GitInvocation[];
|
|
29
|
-
/** True when git may run through something the parser cannot see into (xargs, `$CMD`, …). */
|
|
30
29
|
/** Words of segments that may run git in a way that cannot be read (`$GIT push`, `xargs git …`). */
|
|
31
30
|
readonly opaque: readonly string[];
|
|
32
31
|
};
|
package/src/gates/push-guard.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
isBashToolResult,
|
|
6
6
|
isToolCallEventType,
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { extractCommits } from "../core/commit-command.ts";
|
|
8
9
|
import { parseConventionalCommit } from "../core/commit-message.ts";
|
|
9
10
|
import type { Exec } from "../core/exec.ts";
|
|
10
11
|
import { type PushTarget, pushTargets } from "../core/push-command.ts";
|
|
@@ -177,6 +178,12 @@ export function registerPushGuard(deps: PushGuardDeps): void {
|
|
|
177
178
|
}
|
|
178
179
|
const status = await getTrunkStatus(deps.exec, { branch: trunk, cwd: ctx.cwd });
|
|
179
180
|
if (status.status !== "red") return undefined;
|
|
181
|
+
if (extractCommits(command).length > 0) {
|
|
182
|
+
return {
|
|
183
|
+
block: true,
|
|
184
|
+
reason: `CI on ${trunk} is red and this call commits and pushes together, so the push cannot be judged as a fix for it. Commit first in one call, then push in the next.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
180
187
|
if (await repairsRedTrunk(deps, ctx, loaded.value, status.runId)) return undefined;
|
|
181
188
|
return hardStop(deps, {
|
|
182
189
|
ctx,
|
|
@@ -35,7 +35,9 @@ export const MIX_QUESTION: ClassifierBoolQuestion = {
|
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// Bound the text before redacting (redaction cost grows with run length); keep margin so a secret at the cut is still seen whole.
|
|
39
|
+
const clip = (text: string, max: number): string =>
|
|
40
|
+
redactSecrets(text.slice(0, max * 2)).slice(0, max);
|
|
39
41
|
|
|
40
42
|
export async function judgeCommit(
|
|
41
43
|
jev: Jev,
|
|
@@ -23,9 +23,9 @@ export async function judgeFixRelated(
|
|
|
23
23
|
): Promise<Result<number, JevError>> {
|
|
24
24
|
const asked = await jev.ask(
|
|
25
25
|
{
|
|
26
|
-
failingLog: redactSecrets(input.failingLog).slice(-4000),
|
|
27
|
-
message: redactSecrets(input.message).slice(0, 2000),
|
|
28
|
-
diff: redactSecrets(input.diff).slice(0, 8000),
|
|
26
|
+
failingLog: redactSecrets(input.failingLog.slice(-8000)).slice(-4000),
|
|
27
|
+
message: redactSecrets(input.message.slice(0, 4000)).slice(0, 2000),
|
|
28
|
+
diff: redactSecrets(input.diff.slice(0, 16000)).slice(0, 8000),
|
|
29
29
|
},
|
|
30
30
|
{ related: FIX_RELATED_QUESTION },
|
|
31
31
|
);
|