@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.
@@ -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, naming, report-only. Zero findings is a valid result. End with **Not verified**: checks you could not run.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jwilger/pi-development-system",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
4
4
  "description": "A pi extension package representing a seasoned approach to software development using a full AI SDLC.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -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. Nits never reset the streak; they go to
52
- `docs/decisions/followups.md`.
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
@@ -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
- /** The nits of a round, as a section for docs/decisions/followups.md. */
147
- export function renderFollowups(
148
- slice: SliceRef,
149
- round: number,
150
- findings: readonly Finding[],
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 undefined;
154
- const lines = nits.map((f) => `- ${f.lens}${where(f)} — ${f.summary}`);
155
- return `\n## From ${slice} review round ${round}\n\n${lines.join("\n")}\n`;
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
- renderFollowups,
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, files nits in docs/decisions/followups.md, and returns the clean-round count and the next action.",
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
- // File the nits first: if this fails nothing is recorded and a retry cannot count the round twice.
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
- }