@jwilger/pi-development-system 0.42.0 → 0.44.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/agents/reviewer.md +1 -1
- package/extensions/development-system.ts +0 -2
- package/package.json +1 -1
- package/skills/code-review/SKILL.md +3 -2
- package/src/core/review-flow.ts +10 -9
- package/src/review/review-tools.ts +4 -25
- package/src/context/compaction.ts +0 -29
- package/src/context/state-block.ts +0 -30
package/agents/reviewer.md
CHANGED
|
@@ -53,4 +53,4 @@ Use exactly this packet; the coordinator parses it.
|
|
|
53
53
|
no-blocking | blocking
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Severity: **blocking** = demonstrable defect or broken non-negotiable; **should-fix** = real defect or missing test with a realistic trigger; **nit** = style
|
|
56
|
+
Severity: **blocking** = demonstrable defect or broken non-negotiable; **should-fix** = real defect or missing test with a realistic trigger; **nit** = style or naming; do not report these unless asked, because nothing stores them. Zero findings is a valid result. End with **Not verified**: checks you could not run.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { cadenceLine, DEFAULT_PUSH_MINUTES } from "../src/context/cadence.ts";
|
|
4
|
-
import { registerCompactionResync } from "../src/context/compaction.ts";
|
|
5
4
|
import { appendContextTail, renderContextTail } from "../src/context/context-tail.ts";
|
|
6
5
|
import { registerModelAdvice } from "../src/context/model-advice.ts";
|
|
7
6
|
import { renderStatus, renderStatusLine, STATUS_KEY } from "../src/context/status.ts";
|
|
@@ -114,7 +113,6 @@ export function createDevelopmentSystem(pi: ExtensionAPI) {
|
|
|
114
113
|
});
|
|
115
114
|
registerTestGuard({ pi, state, approvals, jev: (ctx) => jevHolder.forContext(ctx) });
|
|
116
115
|
registerTestEvidence({ pi, state });
|
|
117
|
-
registerCompactionResync({ pi, state });
|
|
118
116
|
registerModelAdvice({
|
|
119
117
|
pi,
|
|
120
118
|
state,
|
package/package.json
CHANGED
|
@@ -48,8 +48,9 @@ The verdict must agree with the findings.
|
|
|
48
48
|
|
|
49
49
|
- **blocking**: a demonstrable defect or a broken non-negotiable.
|
|
50
50
|
- **should-fix**: a real defect or missing test with a realistic trigger.
|
|
51
|
-
- **nit**: style or report-only.
|
|
52
|
-
|
|
51
|
+
- **nit**: style or report-only. Reviewers should not send these. If one arrives it
|
|
52
|
+
never resets the streak, and nothing stores it: fix it now, or drop it and say why
|
|
53
|
+
in your reply. A finding nobody will act on is not worth recording.
|
|
53
54
|
- **false-positive**: refuted; ignored.
|
|
54
55
|
|
|
55
56
|
Jev may raise a finding's severity when it is at least 0.8 confident; it never
|
package/src/core/review-flow.ts
CHANGED
|
@@ -143,16 +143,17 @@ export function adoptSeverity(
|
|
|
143
143
|
const where = (f: Finding): string =>
|
|
144
144
|
f.path === undefined ? "" : ` \`${f.path}${f.line === undefined ? "" : `:${f.line}`}\``;
|
|
145
145
|
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
): string | undefined {
|
|
146
|
+
/**
|
|
147
|
+
* Nits are never stored. Each is reported back so the coordinator fixes it now or drops it
|
|
148
|
+
* with a stated reason; a finding nobody will act on is not worth recording.
|
|
149
|
+
*/
|
|
150
|
+
export function nitLines(findings: readonly Finding[]): string[] {
|
|
152
151
|
const nits = findings.filter((f) => f.severity === "nit");
|
|
153
|
-
if (nits.length === 0) return
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
if (nits.length === 0) return [];
|
|
153
|
+
return [
|
|
154
|
+
"Nits are not stored anywhere: fix each now, or drop it and say why in your reply.",
|
|
155
|
+
...nits.map((f) => `- nit ${f.lens}${where(f)} — ${f.summary}`),
|
|
156
|
+
];
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
/** The task text for a fresh reviewer: slice, lenses, how to see the diff, and the packet to return. */
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import type { ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
2
|
import { type Static, Type } from "typebox";
|
|
5
3
|
import type { Exec } from "../core/exec.ts";
|
|
@@ -16,7 +14,7 @@ import {
|
|
|
16
14
|
import {
|
|
17
15
|
adoptSeverity,
|
|
18
16
|
combinePackets,
|
|
19
|
-
|
|
17
|
+
nitLines,
|
|
20
18
|
reviewerTask,
|
|
21
19
|
reviewLabel,
|
|
22
20
|
reviewOf,
|
|
@@ -209,7 +207,7 @@ export function createReviewRecordTool(
|
|
|
209
207
|
name: "devsys_review_record",
|
|
210
208
|
label: "Record review round",
|
|
211
209
|
description:
|
|
212
|
-
"Record one review round from the reviewer's packet(s): parses findings, lets Jev adjust severity when confident,
|
|
210
|
+
"Record one review round from the reviewer's packet(s): parses findings, lets Jev adjust severity when confident, and returns the clean-round count and the next action.",
|
|
213
211
|
promptSnippet: "Record a reviewer packet as a review round",
|
|
214
212
|
parameters: RecordParameters,
|
|
215
213
|
exposure: "direct",
|
|
@@ -259,27 +257,7 @@ export function createReviewRecordTool(
|
|
|
259
257
|
|
|
260
258
|
const merged = combinePackets(packets);
|
|
261
259
|
const { findings, notes } = await adjusted(deps, ctx, merged.findings, snap.value.sample);
|
|
262
|
-
|
|
263
|
-
const followups = renderFollowups(slice, expected, findings);
|
|
264
|
-
let reviewed = snap.value;
|
|
265
|
-
if (followups !== undefined) {
|
|
266
|
-
try {
|
|
267
|
-
const dir = join(ctx.cwd, "docs", "decisions");
|
|
268
|
-
await mkdir(dir, { recursive: true });
|
|
269
|
-
await appendFile(join(dir, "followups.md"), followups);
|
|
270
|
-
} catch (cause) {
|
|
271
|
-
return reply(
|
|
272
|
-
`could not write docs/decisions/followups.md (${cause instanceof Error ? cause.message : String(cause)}); the round was not recorded, retry`,
|
|
273
|
-
true,
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
// The file is part of the work tree, so writing it changed the diff. The round covers the
|
|
277
|
-
// reviewed code plus its own follow-up note; otherwise `done` would be stale on arrival.
|
|
278
|
-
const after = await snapshotDiff(deps.exec, ctx.cwd, params.diffRange?.trim() || "HEAD");
|
|
279
|
-
if (!after.ok)
|
|
280
|
-
return reply(`cannot read the diff after filing follow-ups: ${after.error}`, true);
|
|
281
|
-
reviewed = after.value;
|
|
282
|
-
}
|
|
260
|
+
const reviewed = snap.value;
|
|
283
261
|
const review = addRound(
|
|
284
262
|
{ ...before, required },
|
|
285
263
|
{
|
|
@@ -298,6 +276,7 @@ export function createReviewRecordTool(
|
|
|
298
276
|
`Round ${review.rounds.length} recorded: ${counts("blocking")} blocking, ${counts("should-fix")} should-fix, ${counts("nit")} nit, ${counts("false-positive")} false-positive.`,
|
|
299
277
|
reviewLabel(review),
|
|
300
278
|
...notes,
|
|
279
|
+
...nitLines(findings),
|
|
301
280
|
`next: ${nextAction(review, reviewed.digest)}`,
|
|
302
281
|
...(range === "HEAD" ? [] : [GATE_RANGE_NOTE]),
|
|
303
282
|
].join("\n"),
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { SessionState } from "../state/session-state.ts";
|
|
3
|
-
import { renderStateBlock } from "./state-block.ts";
|
|
4
|
-
|
|
5
|
-
export const RESYNC_ENTRY_TYPE = "devsys-resync";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* pi lets `session_before_compact` only REPLACE the whole summary (summary, firstKeptEntryId,
|
|
9
|
-
* tokensBefore), which would mean writing our own summariser and paying another model call. So the
|
|
10
|
-
* summary is left to pi, and once it lands we re-state the working state as one message the next
|
|
11
|
-
* request sees. State itself lives in session entries and the per-turn context tail, so nothing is
|
|
12
|
-
* lost; this just makes it visible to the model again.
|
|
13
|
-
*/
|
|
14
|
-
export function registerCompactionResync(deps: { pi: ExtensionAPI; state: SessionState }): void {
|
|
15
|
-
deps.pi.on("session_compact", () => {
|
|
16
|
-
const state = deps.state.get();
|
|
17
|
-
if (state.phase === "idle" && state.openDepartures.length === 0) return;
|
|
18
|
-
// triggerTurn:false: while a run is active pi would otherwise steer the message, and the queued
|
|
19
|
-
// message would force an extra model turn after automatic compaction.
|
|
20
|
-
deps.pi.sendMessage(
|
|
21
|
-
{
|
|
22
|
-
customType: RESYNC_ENTRY_TYPE,
|
|
23
|
-
content: `[devsys resync] Context was compacted. Current working state:\n\n${renderStateBlock(state)}`,
|
|
24
|
-
display: true,
|
|
25
|
-
},
|
|
26
|
-
{ triggerTurn: false },
|
|
27
|
-
);
|
|
28
|
-
});
|
|
29
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { DevsysState } from "../core/types.ts";
|
|
2
|
-
import { activeReview } from "./status.ts";
|
|
3
|
-
|
|
4
|
-
const MAX_DEPARTURES = 8;
|
|
5
|
-
|
|
6
|
-
/** The durable working state as a short markdown block, for compaction resync and the like. */
|
|
7
|
-
export function renderStateBlock(state: DevsysState): string {
|
|
8
|
-
const departures = state.openDepartures.map(
|
|
9
|
-
(d) => ` - ${d.gate} — ${d.chosen.replace(/\s+/g, " ")}`,
|
|
10
|
-
);
|
|
11
|
-
const shown =
|
|
12
|
-
departures.length <= MAX_DEPARTURES
|
|
13
|
-
? departures
|
|
14
|
-
: [
|
|
15
|
-
...departures.slice(0, MAX_DEPARTURES - 1),
|
|
16
|
-
` - …and ${departures.length - MAX_DEPARTURES + 1} more`,
|
|
17
|
-
];
|
|
18
|
-
const run = state.lastTestRun;
|
|
19
|
-
return [
|
|
20
|
-
"## Development System State",
|
|
21
|
-
`- phase: ${state.phase}`,
|
|
22
|
-
`- sizing: ${state.sizing ?? "unset"}`,
|
|
23
|
-
`- slice: ${state.activeSlice ?? "none"}`,
|
|
24
|
-
`- open departures: ${state.openDepartures.length === 0 ? "none" : state.openDepartures.length}`,
|
|
25
|
-
...shown,
|
|
26
|
-
`- ${activeReview(state) ?? "review: none"}`,
|
|
27
|
-
`- last test run: ${run === undefined ? "none" : `exit ${run.exitCode} — ${run.summary}`}`,
|
|
28
|
-
`- last push: ${state.lastPushAt ?? "none"}`,
|
|
29
|
-
].join("\n");
|
|
30
|
-
}
|