@jwilger/pi-development-system 0.63.0 → 0.65.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/package.json
CHANGED
|
@@ -125,8 +125,10 @@ async function reviewNeeds(
|
|
|
125
125
|
ctx: ExtensionContext,
|
|
126
126
|
command: string,
|
|
127
127
|
): Promise<Need[]> {
|
|
128
|
-
const { phase, activeSlice } = deps.state.get();
|
|
128
|
+
const { phase, activeSlice, sizing } = deps.state.get();
|
|
129
129
|
if ((phase !== "implementing" && phase !== "reviewing") || activeSlice === undefined) return [];
|
|
130
|
+
// A fix is sized without a review artifact (I8.1); the gate must not demand rounds the size never asked for.
|
|
131
|
+
if (sizing === "fix") return [];
|
|
130
132
|
const snap = await snapshotDiff(deps.exec, ctx.cwd, "HEAD");
|
|
131
133
|
// An unreadable diff cannot be compared; the gate asks for a departure rather than guessing.
|
|
132
134
|
const gap = reviewGap(
|
|
@@ -95,8 +95,9 @@ export async function judgeSizing(
|
|
|
95
95
|
sizing: label,
|
|
96
96
|
confidence: sizing.confidence,
|
|
97
97
|
artifactNeed: {
|
|
98
|
-
"
|
|
99
|
-
|
|
98
|
+
// Fixed by size, never Jev's call; a nonzero need would put them in every "Also consider".
|
|
99
|
+
"task-record": 0,
|
|
100
|
+
review: 0,
|
|
100
101
|
"adr-if-needed": p("adr-if-needed"),
|
|
101
102
|
brief: p("brief"),
|
|
102
103
|
"brief-lite": p("brief"),
|
|
@@ -28,8 +28,8 @@ type Section = (typeof SECTIONS)[number];
|
|
|
28
28
|
|
|
29
29
|
const HEADER = /^## (\S+) [—-] (.+)$/;
|
|
30
30
|
const SECTION_LINE = /^\*\*([^:*]+)(?::\*\*|\*\*:)\s*(.*)$/;
|
|
31
|
-
/** Top-level list items only;
|
|
32
|
-
const STEP = /^(?:\d+[.)]|[
|
|
31
|
+
/** Top-level list items only (CommonMark allows up to three spaces of indent); deeper items belong to the step above. */
|
|
32
|
+
const STEP = /^ {0,3}(?:\d+[.)]|[-*+])\s+(.*)$/;
|
|
33
33
|
const STEPS_MIN = 3;
|
|
34
34
|
const STEPS_MAX = 7;
|
|
35
35
|
/** Words that stand in for a command or an observable result. */
|
|
@@ -88,7 +88,19 @@ function splitSections(lines: readonly string[]): Sections {
|
|
|
88
88
|
found.set(name, current);
|
|
89
89
|
} else current?.push(line);
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
// Leading indentation is kept so the first list item of a section is read like the others.
|
|
92
|
+
const text = new Map(
|
|
93
|
+
[...found].map(
|
|
94
|
+
([name, body]) =>
|
|
95
|
+
[
|
|
96
|
+
name,
|
|
97
|
+
body
|
|
98
|
+
.join("\n")
|
|
99
|
+
.replace(/^\s*\n/, "")
|
|
100
|
+
.trimEnd(),
|
|
101
|
+
] as const,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
92
104
|
return { text, duplicates };
|
|
93
105
|
}
|
|
94
106
|
|
|
@@ -131,9 +143,14 @@ const stripTicks = (text: string): string => {
|
|
|
131
143
|
const stepsOf = (text: string): string[] => {
|
|
132
144
|
const lines = text.split("\n");
|
|
133
145
|
const fenced = fencedFlags(lines);
|
|
146
|
+
// The first item sets the list's indent; an item two or more columns deeper is nested under a step.
|
|
147
|
+
let base: number | undefined;
|
|
134
148
|
return lines.flatMap((line, at) => {
|
|
135
149
|
const item = fenced[at] === true ? undefined : STEP.exec(line)?.[1]?.trim();
|
|
136
|
-
|
|
150
|
+
if (item === undefined || item === "") return [];
|
|
151
|
+
const indent = line.length - line.trimStart().length;
|
|
152
|
+
base ??= indent;
|
|
153
|
+
return indent >= base + 2 ? [] : [item];
|
|
137
154
|
});
|
|
138
155
|
};
|
|
139
156
|
|
|
@@ -148,6 +165,7 @@ function concrete(name: "Run" | "Expected", text: string): string | undefined {
|
|
|
148
165
|
}
|
|
149
166
|
|
|
150
167
|
const get = (sections: ReadonlyMap<Section, string>, s: Section): string => sections.get(s) ?? "";
|
|
168
|
+
const got = (sections: ReadonlyMap<Section, string>, s: Section): string => get(sections, s).trim();
|
|
151
169
|
|
|
152
170
|
/** Every readiness problem in the sections, so the author fixes them in one pass. */
|
|
153
171
|
function problemsOf(markdown: string, split: Sections): string[] {
|
|
@@ -156,7 +174,7 @@ function problemsOf(markdown: string, split: Sections): string[] {
|
|
|
156
174
|
if (split.duplicates.length > 0) {
|
|
157
175
|
problems.push(`duplicate section: ${[...new Set(split.duplicates)].join(", ")}`);
|
|
158
176
|
}
|
|
159
|
-
const missing = SECTIONS.filter((s) =>
|
|
177
|
+
const missing = SECTIONS.filter((s) => got(sections, s) === "");
|
|
160
178
|
if (missing.length > 0) problems.push(`missing section: ${missing.join(", ")}`);
|
|
161
179
|
if (/\bTBD\b/.test(markdown)) problems.push("contains TBD; decide it or split the task");
|
|
162
180
|
const steps = stepsOf(get(sections, "Steps"));
|
|
@@ -164,7 +182,7 @@ function problemsOf(markdown: string, split: Sections): string[] {
|
|
|
164
182
|
problems.push(`Steps must be ${STEPS_MIN}-${STEPS_MAX} steps, found ${steps.length}`);
|
|
165
183
|
}
|
|
166
184
|
for (const name of ["Run", "Expected"] as const) {
|
|
167
|
-
const why = missing.includes(name) ? undefined : concrete(name,
|
|
185
|
+
const why = missing.includes(name) ? undefined : concrete(name, got(sections, name));
|
|
168
186
|
if (why !== undefined) problems.push(why);
|
|
169
187
|
}
|
|
170
188
|
return problems;
|
|
@@ -202,9 +220,9 @@ export function parseTaskRecord(markdown: string, id?: string): TaskRecord | Par
|
|
|
202
220
|
const found = locate(markdown.replace(/\r\n?/g, "\n"), id);
|
|
203
221
|
if (isParseError(found)) return found;
|
|
204
222
|
const split = splitSections(found.lines);
|
|
205
|
-
const problems = problemsOf(found.lines.join("\n")
|
|
223
|
+
const problems = problemsOf(`${found.id} ${found.title}\n${found.lines.join("\n")}`, split);
|
|
206
224
|
if (problems.length > 0) return parseError(problems.join("; "));
|
|
207
|
-
const text = (s: Section): string =>
|
|
225
|
+
const text = (s: Section): string => got(split.text, s);
|
|
208
226
|
return {
|
|
209
227
|
id: found.id,
|
|
210
228
|
title: found.title,
|
|
@@ -212,7 +230,7 @@ export function parseTaskRecord(markdown: string, id?: string): TaskRecord | Par
|
|
|
212
230
|
files: filesOf(text("Files")),
|
|
213
231
|
interfaces: text("Interfaces"),
|
|
214
232
|
firstFailingTest: text("First failing test"),
|
|
215
|
-
steps: stepsOf(text
|
|
233
|
+
steps: stepsOf(get(split.text, "Steps")),
|
|
216
234
|
run: stripTicks(text("Run")),
|
|
217
235
|
expected: text("Expected"),
|
|
218
236
|
outOfScope: text("Out of scope"),
|