@jwilger/pi-development-system 0.8.4 → 0.8.5
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
|
@@ -101,10 +101,15 @@ export function findForbiddenTrailerKeys(text: string): string[] {
|
|
|
101
101
|
return [...text.matchAll(AI_TRAILER_ANYWHERE)].map((m) => m[0].trim());
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
const
|
|
105
|
-
paragraph.split("\n").every((line) => /^\s*(?:[-*+]|\d+[.)])\s/.test(line));
|
|
106
|
-
|
|
104
|
+
const BULLET = /^\s*(?:[-*+]|\d+[.)])\s+/;
|
|
107
105
|
const wordCount = (text: string): number => text.split(/\s+/).filter((w) => w !== "").length;
|
|
106
|
+
const SHORT_BULLET_WORDS = 5;
|
|
107
|
+
|
|
108
|
+
/** A list of file names or other terse items; bullets that explain themselves are prose. */
|
|
109
|
+
const isTerseList = (paragraph: string): boolean =>
|
|
110
|
+
paragraph
|
|
111
|
+
.split("\n")
|
|
112
|
+
.every((line) => BULLET.test(line) && wordCount(line.replace(BULLET, "")) < SHORT_BULLET_WORDS);
|
|
108
113
|
|
|
109
114
|
const PROSE_TRAILER_WORDS = 5;
|
|
110
115
|
|
|
@@ -124,6 +129,6 @@ export function hasRationaleBody(message: string): boolean {
|
|
|
124
129
|
?.split("\n") ?? [];
|
|
125
130
|
const proseTrailer = trailers.length > 0 && trailerBlockLines.some(isProseTrailer);
|
|
126
131
|
return (
|
|
127
|
-
proseTrailer || body.some((paragraph) => !
|
|
132
|
+
proseTrailer || body.some((paragraph) => !isTerseList(paragraph) && wordCount(paragraph) >= 5)
|
|
128
133
|
);
|
|
129
134
|
}
|
package/src/core/git-intent.ts
CHANGED
|
@@ -58,14 +58,24 @@ export function splitLines(command: string): string[] {
|
|
|
58
58
|
|
|
59
59
|
const isRedirect = (op: string): boolean => /^[<>]/.test(op) && !op.endsWith("(");
|
|
60
60
|
|
|
61
|
-
/** Tokens of one line grouped into simple commands (operators separate segments). */
|
|
61
|
+
/** Tokens of one line grouped into simple commands (operators separate segments; redirects and their operands are dropped). */
|
|
62
62
|
export function segments(line: string): string[][] {
|
|
63
63
|
const result: string[][] = [[]];
|
|
64
|
+
let skipOperand = false;
|
|
64
65
|
for (const token of parseShell(line, (name) => `$${name}`)) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
const current = result[result.length - 1];
|
|
67
|
+
if (typeof token === "string" || "pattern" in token) {
|
|
68
|
+
const text = typeof token === "string" ? token : token.pattern;
|
|
69
|
+
if (!skipOperand) current?.push(text);
|
|
70
|
+
skipOperand = false;
|
|
71
|
+
} else if ("op" in token && isRedirect(token.op)) {
|
|
72
|
+
// `2>&1`, `>/dev/null`: the file descriptor before it and the target after it are not arguments.
|
|
73
|
+
if (/^\d+$/.test(current?.at(-1) ?? "")) current?.pop();
|
|
74
|
+
skipOperand = true;
|
|
75
|
+
} else if (!("comment" in token)) {
|
|
76
|
+
result.push([]);
|
|
77
|
+
skipOperand = false;
|
|
78
|
+
}
|
|
69
79
|
}
|
|
70
80
|
return result.filter((s) => s.length > 0);
|
|
71
81
|
}
|
|
@@ -63,7 +63,9 @@ function messageNeeds(message: string): Need[] {
|
|
|
63
63
|
|
|
64
64
|
/** `-a`/`--all`, or a `git add` in the same command, widen the commit beyond what is staged. */
|
|
65
65
|
const stagesEverything = (command: string): boolean =>
|
|
66
|
-
|
|
66
|
+
/\bgit\b[^;&|\n]*\bcommit\b[^;&|\n]*\s(?:-[a-zA-Z]*a[a-zA-Z]*|--all|--include)(?:\s|$)|\bgit\s+add\s+(?:-A|--all|-u|\.)(?:\s|$)/.test(
|
|
67
|
+
command,
|
|
68
|
+
);
|
|
67
69
|
|
|
68
70
|
/** What is about to be committed: the staged changes, or every tracked change when the commit stages them itself. */
|
|
69
71
|
async function pendingDiff(
|
package/src/gates/push-guard.ts
CHANGED
|
@@ -66,7 +66,7 @@ async function pushesTrunk(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/** Whether the unpushed work is a `fix` commit whose diff Jev reads as only repairing the red build. */
|
|
69
|
-
async function
|
|
69
|
+
async function judgeRepair(
|
|
70
70
|
deps: PushGuardDeps,
|
|
71
71
|
ctx: ExtensionContext,
|
|
72
72
|
config: DevsysConfig,
|
|
@@ -94,6 +94,20 @@ async function repairsRedTrunk(
|
|
|
94
94
|
return judged.ok && judged.value >= FIX_RELATED_THRESHOLD;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** Fails closed: any exec failure means the push is not shown to repair the red build. */
|
|
98
|
+
async function repairsRedTrunk(
|
|
99
|
+
deps: PushGuardDeps,
|
|
100
|
+
ctx: ExtensionContext,
|
|
101
|
+
config: DevsysConfig,
|
|
102
|
+
runId: number | undefined,
|
|
103
|
+
): Promise<boolean> {
|
|
104
|
+
try {
|
|
105
|
+
return await judgeRepair(deps, ctx, config, runId);
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
type HardStop = {
|
|
98
112
|
readonly ctx: ExtensionContext;
|
|
99
113
|
readonly toolCallId: string;
|