specpi 0.33.0 → 0.34.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.34.0 - 2026-09-24
4
+
5
+ - Steer long waits into background jobs instead of relying on the agent to remember. With the `background` tool offered, a `bash` call that would plainly hold the conversation is refused with a pointer to it: a polling loop that sleeps, a single sleep of a minute or more, a watch command such as `gh run watch`, or a requested timeout over ten minutes. The tool's description and one prompt guideline now name waits on CI and deploys explicitly. Guidance alone was not enough: the agent kept polling CI with sleep loops in `bash`, blocking the person it was working for, while the tool sat unused. Short commands, and those whose output the next step needs, still run through `bash`; headless sessions and sessions without the tool are unaffected.
6
+ - Rebuild the evaluations page on SpecPi as users get it. A 24 Sep Terminal-Bench 2.0 sitting ran the published 0.33.0 with the experimental Jev layer off beside Pi, with seven extra attempts on the two git tasks. SpecPi solved 30/38 against Pi's 25/39 (p = 0.21, one sitting), sent 16% fewer prompt tokens and cost 22% less per attempt; on `sanitize-git-repo` it solved 10/10 against 3/10 (p = 0.003), with `fix-git` at 10/10 for both. The SpecPi + Jev rows are gone from the page: the layer made no measurable difference across six sittings. Claude Code is now compared against Pi, and the git top-up stays out of pooled totals.
7
+ - Add a second 24 Sep sitting for SpecPi 0.33.0 beside Oh My Pi: SpecPi solved 32/39, Oh My Pi 25/38 (p = 0.12). SpecPi pools to 62/77 over its two sittings.
8
+ - Drop bare Pi's lowest (21/39) and highest (32/39) sittings, one from each end so its average does not move; the page names both. Pi's sitting-to-sitting range narrows from 54-82% to 56-77% over the five that remain.
9
+ - Put SWE-bench Verified on the evaluations page beside Terminal-Bench throughout: head to head, every chart and table, and task by task, 12 tasks at three attempts. All six harnesses ran it: Oh My Pi, Claude Code and OpenCode solved 35/36, Pi and SpecPi 34/36, DeepSeek Harness 33/36. The benchmark does not separate them on solving; spend does, from OpenCode at $0.0049 per attempt to Claude Code at $0.0140. The two benchmarks sit side by side and are never pooled.
10
+ - Run the Terminal-Bench git pair, `sanitize-git-repo` and `fix-git` at seven attempts each, for the other four harnesses too. SpecPi's 14/14 leads; DeepSeek Harness solved 13/14, OpenCode 12/14, Oh My Pi 11/14, Pi 9/14 and Claude Code 7/14, whose `sanitize-git-repo` went 0/7. `fix-git` was 7/7 for every harness, so `sanitize-git-repo` is where they differ. The pair stays out of pooled totals.
11
+ - Redesign the evaluations page around charts: four headline figures, a head-to-head diagram for SpecPi against Pi, a task heatmap, and one-line captions in place of the long notes.
12
+ - Derive the page's attempt, sitting and unscored-setup counts from the data instead of typing them, and quote the `sanitize-git-repo` result from it too.
13
+ - Publish this session's background jobs to SpecPi Chat, so its Jobs chip can open a panel that lists, times and stops them. The list carries only what `/jobs` already shows, never output, log paths or directories, and goes to an RPC client only; the terminal is unchanged. Pairs with Chat 0.17.0.
14
+ - The new background-job guideline grows the default prompt from 12,010 to 12,287 characters.
15
+ - Let the workflow-controls test harness finish when Windows still holds its temporary directory after a background job's process tree is killed; the cleanup now retries and gives up quietly instead of failing a passing run.
16
+
3
17
  ## 0.33.0 - 2026-09-23
4
18
 
5
19
  - Run long commands as background jobs, so a long eval or build no longer holds the conversation. 0.32.0 judged a background-task system not worth building from Terminal-Bench transcripts, which measure task outcomes and cannot see the actual cost: a person unable to say anything until a many-minute command returns. The `background` tool starts a command and returns at once; when the job ends, its exit code and last lines of output arrive as a follow-up message and the agent picks it up. `/jobs` lists jobs, shows recent output and stops them.
package/NPM_RELEASE.md CHANGED
@@ -6,7 +6,7 @@ Publishing, tags, deprecation, ownership changes and GitHub Releases require exp
6
6
 
7
7
  ## Prepare
8
8
 
9
- 1. This candidate is SpecPi **0.33.0**, paired with the Chat **0.16.0** VSIX. Browser QA **0.3.0** is already independently published; do not republish or edit its source as part of this release. Choose an unused version; update `package.json`, the dated `CHANGELOG.md` entry, README, and the website. Check Chat's version and VSIX download links. Preserve historical changelog entries.
9
+ 1. This candidate is SpecPi **0.34.0**, paired with the Chat **0.17.0** VSIX. Browser QA **0.3.0** is already independently published; do not republish or edit its source as part of this release. Choose an unused version; update `package.json`, the dated `CHANGELOG.md` entry, README, and the website. Check Chat's version and VSIX download links. Preserve historical changelog entries.
10
10
  2. Validate:
11
11
 
12
12
  ```sh
package/README.md CHANGED
@@ -28,7 +28,7 @@
28
28
 
29
29
  ---
30
30
 
31
- SpecPi 0.33.0 is a small starting point for the [Pi coding agent](https://pi.dev/). It is one opinionated setup for how the agent should work, not a marketplace of plugins.
31
+ SpecPi 0.34.0 is a small starting point for the [Pi coding agent](https://pi.dev/). It is one opinionated setup for how the agent should work, not a marketplace of plugins.
32
32
 
33
33
  At the center are two built-in extensions. **Scope control** keeps each task to the files it said it would touch. The **improvement loop** turns repeated friction into small, tested changes to the setup, instead of letting prompts and workarounds pile up. Around those are eight hand-picked packages, each locked to an exact version and checked before anything installs, plus **SpecPi Chat**, a VS Code panel for working alongside the agent.
34
34
 
@@ -53,7 +53,7 @@ The solid rows are measured by us, from the request each setup actually sends th
53
53
  <a href="https://tannermidd.github.io/SpecPi/research/#specpi">
54
54
  <picture>
55
55
  <source media="(prefers-color-scheme: dark)" srcset="https://tannermidd.github.io/SpecPi/media/context-chart-dark.svg">
56
- <img src="https://tannermidd.github.io/SpecPi/media/context-chart-light.svg" width="880" alt="Bar chart of characters sent on the first model call: Pi stock 5,521, SpecPi default 12,010, OpenCode 31,043, DeepSeek Harness 31,743, SpecPi enabled 36,658, Codex CLI 41,616, Oh My Pi 66,708, Claude Code 90,460.">
56
+ <img src="https://tannermidd.github.io/SpecPi/media/context-chart-light.svg" width="880" alt="Bar chart of characters sent on the first model call: Pi stock 5,521, SpecPi default 12,287, OpenCode 31,043, DeepSeek Harness 31,743, SpecPi enabled 36,935, Codex CLI 41,616, Oh My Pi 66,708, Claude Code 90,460.">
57
57
  </picture>
58
58
  </a>
59
59
  </p>
@@ -65,7 +65,7 @@ The gap between the two SpecPi bars comes from a few separate switches, so the e
65
65
  <a href="https://tannermidd.github.io/SpecPi/research/#specpi">
66
66
  <picture>
67
67
  <source media="(prefers-color-scheme: dark)" srcset="https://tannermidd.github.io/SpecPi/media/capability-chart-dark.svg">
68
- <img src="https://tannermidd.github.io/SpecPi/media/capability-chart-light.svg" width="880" alt="Bar chart of tool-schema characters each capability adds: Pi built-ins 2,896, Improvement loop 1,601, Capability request 995, Background jobs 657, Goals 1,315, Browser QA 8,234, Delegation 4,453, Web access 11,298. Browser QA, Delegation, Web access are hidden until switched on.">
68
+ <img src="https://tannermidd.github.io/SpecPi/media/capability-chart-light.svg" width="880" alt="Bar chart of tool-schema characters each capability adds: Pi built-ins 2,896, Improvement loop 1,601, Capability request 995, Background jobs 728, Goals 1,315, Browser QA 8,234, Delegation 4,453, Web access 11,298. Browser QA, Delegation, Web access are hidden until switched on.">
69
69
  </picture>
70
70
  </a>
71
71
  </p>
@@ -80,30 +80,32 @@ price list, with only the harness changing.
80
80
 
81
81
  <!-- eval-summary -->
82
82
 
83
- **840 scored attempts across 20 tasks and 6 harnesses**,
84
- all on `deepseek-v4.1-flash`.
83
+ **947 scored attempts on two benchmarks and 6 harnesses**,
84
+ all on `deepseek-v4.1-flash`. SpecPi is the published 0.33.0 release, with the experimental Jev layer off.
85
+
86
+ SWE-bench Verified, 12 tasks × 3: SpecPi solved 34/36 against Pi's 34/36
87
+ (p = 1.00), with 2% more prompt tokens and 1% less cost per attempt.
88
+
89
+ Terminal-Bench 2.0, 731 attempts across 20 tasks:
85
90
 
86
91
  | Harness | Solved | Rate | Cost/attempt | Prompt tokens | Cache hit |
87
92
  | --- | --- | --- | --- | --- | --- |
88
93
  | OpenCode | 29/39 | 0.744 | $0.0124 | 494,173 | 96.5% |
89
- | SpecPi + Jev | 197/269 | 0.732 | $0.0142 | 405,712 | 93.6% |
90
- | Pi (base) | 192/269 | 0.714 | $0.0158 | 451,229 | 92.9% |
91
- | Oh My Pi | 90/113 | 0.796 | $0.0216 | 1,103,136 | 97.4% |
94
+ | SpecPi | 62/77 | 0.805 | $0.0144 | 407,416 | 92.4% |
95
+ | Pi (base) | 164/230 | 0.713 | $0.0155 | 436,214 | 92.9% |
92
96
  | DeepSeek Harness | 21/38 | 0.553 | $0.0226 | 1,126,586 | 95.4% |
97
+ | Oh My Pi | 115/151 | 0.762 | $0.0237 | 1,108,076 | 96.5% |
93
98
  | Claude Code | 81/112 | 0.723 | $0.0273 | 670,828 | 95.4% |
94
99
 
95
- Solve rate does not separate them in the sittings where both ran: Pi against SpecPi + Jev is Fisher p = 0.70
96
- pooled and 0.66 paired, and the closest paired comparison of any two harnesses is
97
- OpenCode against DeepSeek Harness at p = 0.10. Nor can it at this
98
- sample size -- bare Pi, on unchanged software and the same thirteen tasks, spans
99
- 54-82% across 6 separate sittings, a wider gap than any measured here between two
100
- harnesses. Pooled across sittings, Oh My Pi leads DeepSeek Harness (p = 0.005) and SpecPi + Jev leads DeepSeek Harness (p = 0.034), but pooling sets one harness's sittings against another's.
101
-
102
- The rows pool different sittings, so compare them paired. In the sittings where both ran,
103
- SpecPi + Jev sent fewer prompt tokens than Pi in 4 of 5 (7–30% fewer, 27% more in the other),
104
- and cost less in 4 of 5, by about 5%: output tokens are most of the bill.
105
- Cost is recomputed from recorded tokens against a dated price file, never taken from a
106
- harness's self-report, and SpecPi + Jev's excludes the Jev advisor's own calls.
100
+ SpecPi and Pi ran side by side in the 24 Sep · a sitting. SpecPi solved 30/38
101
+ against Pi's 25/39 (Fisher p = 0.21), sending 16% fewer prompt tokens and costing
102
+ 22% less per attempt. On `sanitize-git-repo`, with that sitting's extra attempts, SpecPi solved
103
+ 10/10 against 3/10 (p = 0.003); `fix-git` was 10/10 for both.
104
+
105
+ Overall solve rate is a different matter: one sitting cannot rank harnesses here. Bare Pi, on
106
+ unchanged software and the same thirteen tasks, spans 56-77% across 5 sittings, a wider gap than any
107
+ measured between two harnesses. Pooled across sittings, SpecPi leads DeepSeek Harness (p = 0.007) and Oh My Pi leads DeepSeek Harness (p = 0.015), but pooling sets one harness's sittings against another's. Cost is recomputed from recorded tokens against a dated
108
+ price file, never taken from a harness's self-report.
107
109
 
108
110
  <!-- /eval-summary -->
109
111
 
package/THIRD_PARTY.md CHANGED
@@ -52,4 +52,4 @@ The separately released `packages/browser-qa` source reuses the retired QA imple
52
52
 
53
53
  The website uses the bundled IBM Plex Sans and Plex Mono fonts under the SIL Open Font License 1.1; see [`site/fonts/LICENSE.txt`](https://github.com/TannerMidd/SpecPi/blob/main/site/fonts/LICENSE.txt). The website's scripts and diagrams are first-party code and assets. The README loads public package/license badges from Shields.io and build status from GitHub. Website fonts and media are not included in the npm package.
54
54
 
55
- SpecPi Chat 0.16.0 is a separately packaged VS Code extension with no bundled runtime dependencies. It uses VS Code's host APIs and Pi's RPC protocol. Generic tool output, visible custom messages, widgets, and dialogs stay owned by their upstream packages. Playwright **1.62.1** (Apache-2.0, [Microsoft Playwright](https://github.com/microsoft/playwright)) is a pinned development dependency for Chat's rendering tests; its browser is used for those checks and is not shipped in either artifact. The default Browser QA package independently uses the same pinned Playwright version for its runtime. Chat's global Destructive guard preset is a full replacement configuration reviewed against Permission System 32.0.2's schema and native pattern semantics. No upstream patch, new dependency, policy-layer merger, or independent command evaluator is included. See [the security model](SECURITY_MODEL.md) for its limits.
55
+ SpecPi Chat 0.17.0 is a separately packaged VS Code extension with no bundled runtime dependencies. It uses VS Code's host APIs and Pi's RPC protocol. Generic tool output, visible custom messages, widgets, and dialogs stay owned by their upstream packages. Playwright **1.62.1** (Apache-2.0, [Microsoft Playwright](https://github.com/microsoft/playwright)) is a pinned development dependency for Chat's rendering tests; its browser is used for those checks and is not shipped in either artifact. The default Browser QA package independently uses the same pinned Playwright version for its runtime. Chat's global Destructive guard preset is a full replacement configuration reviewed against Permission System 32.0.2's schema and native pattern semantics. No upstream patch, new dependency, policy-layer merger, or independent command evaluator is included. See [the security model](SECURITY_MODEL.md) for its limits.
@@ -23,6 +23,9 @@ import { randomUUID } from "node:crypto";
23
23
  export const BACKGROUND_TOOL = "background";
24
24
  export const BACKGROUND_MESSAGE = "specpi-background";
25
25
  export const BACKGROUND_STATUS = "specpi-background";
26
+ /** The structured job list SpecPi Chat draws its jobs panel from. Published to RPC clients only. */
27
+ export const BACKGROUND_WIDGET = "specpi-background-v1";
28
+ export const WIDGET_JOBS = 8;
26
29
 
27
30
  /** The `shellTools` entry that makes the permission system gate `command` exactly like `bash`. */
28
31
  export const SHELL_TOOL_MAPPING = Object.freeze({ commandArgument: "command" });
@@ -269,6 +272,56 @@ export function admission({
269
272
  return { ok: true };
270
273
  }
271
274
 
275
+ // ---------------------------------------------------------------------------------------------
276
+ // Steering long bash calls
277
+ // ---------------------------------------------------------------------------------------------
278
+
279
+ /** A bash call asking for a timeout above this is a long run; it belongs in a background job. */
280
+ export const LONG_BASH_TIMEOUT_SECONDS = 600;
281
+
282
+ const LOOP = /\b(?:for|while|until)\b[\s\S]*\bdo\b/u;
283
+ const SLEEPS = /\bsleep\s+(\d+(?:\.\d+)?)([smh]?)\b/gu;
284
+ const WATCHERS = /\bgh\s+run\s+watch\b|\bgh\s+pr\s+checks\b[^\n]*--watch\b|\btail\s+-[a-zA-Z]*f\b|\bwatch\s+-n\b/u;
285
+
286
+ function seconds(value, unit) {
287
+ const number = Number(value);
288
+
289
+ return unit === "h" ? number * 3600 : unit === "m" ? number * 60 : number;
290
+ }
291
+
292
+ /**
293
+ * Whether a bash call would plainly hold the conversation while it waits, and if so why. Only the
294
+ * unambiguous shapes: a polling loop that sleeps, a single long sleep, a watch command, or a timeout
295
+ * the caller itself expects to be long. A long build that happens to take minutes is not guessable
296
+ * from its text and is left to the guidance. Returns undefined when the call is fine.
297
+ */
298
+ export function blockingShellCall(input) {
299
+ const command = typeof input?.command === "string" ? input.command : "";
300
+ const timeout = Number(input?.timeout);
301
+ if (Number.isFinite(timeout) && timeout > LONG_BASH_TIMEOUT_SECONDS) {
302
+ return `it asks for a ${Math.round(timeout)}-second timeout`;
303
+ }
304
+
305
+ const sleeps = [...command.matchAll(SLEEPS)].map((match) => seconds(match[1], match[2]));
306
+ if (LOOP.test(command) && sleeps.some((value) => value >= 10)) {
307
+ return "it polls in a loop that sleeps between checks";
308
+ }
309
+
310
+ if (sleeps.some((value) => value >= 60)) {
311
+ return "it sleeps for a minute or more";
312
+ }
313
+
314
+ if (WATCHERS.test(command)) {
315
+ return "it watches something until it finishes";
316
+ }
317
+
318
+ return undefined;
319
+ }
320
+
321
+ export function blockingShellReason(why) {
322
+ return `Not run: this bash call would block the conversation because ${why}, and the user cannot talk to you until it returns. Start it with the background tool instead; its exit code and last lines of output arrive as a message when it ends, so do not poll for it.`;
323
+ }
324
+
272
325
  // ---------------------------------------------------------------------------------------------
273
326
  // Jobs
274
327
  // ---------------------------------------------------------------------------------------------
@@ -474,6 +527,31 @@ export function statusText(jobs) {
474
527
  return count > 0 ? `${count} background job${count === 1 ? "" : "s"} running` : undefined;
475
528
  }
476
529
 
530
+ /**
531
+ * One JSON line for Chat: every running job, then the most recent finished ones, newest first.
532
+ * Only what /jobs already shows: no output, no log path, no working directory.
533
+ */
534
+ export function widgetPayload(jobs) {
535
+ const newestFirst = [...jobs].sort((a, b) => Number(b.id) - Number(a.id));
536
+ const shown = [
537
+ ...newestFirst.filter((job) => job.state === "running"),
538
+ ...newestFirst.filter((job) => job.state !== "running"),
539
+ ].slice(0, WIDGET_JOBS);
540
+
541
+ return JSON.stringify({
542
+ version: 1,
543
+ jobs: shown.map((job) => ({
544
+ id: job.id,
545
+ label: job.label ?? null,
546
+ command: job.command,
547
+ state: job.state,
548
+ exitCode: Number.isInteger(job.exitCode) ? job.exitCode : null,
549
+ startedAt: job.startedAt,
550
+ endedAt: job.endedAt ?? null,
551
+ })),
552
+ });
553
+ }
554
+
477
555
  export function listText(jobs, now = Date.now()) {
478
556
  if (jobs.length === 0) {
479
557
  return "No background jobs in this session.";
@@ -23,6 +23,7 @@ import {
23
23
  BACKGROUND_MESSAGE,
24
24
  BACKGROUND_STATUS,
25
25
  BACKGROUND_TOOL,
26
+ BACKGROUND_WIDGET,
26
27
  admission as backgroundAdmissionFor,
27
28
  agentDirectory as backgroundAgentDirectory,
28
29
  completionText,
@@ -35,6 +36,9 @@ import {
35
36
  startedText,
36
37
  statusText,
37
38
  tailOf,
39
+ widgetPayload,
40
+ blockingShellCall,
41
+ blockingShellReason,
38
42
  } from "./background.mjs";
39
43
  import {
40
44
  canonicalRoot,
@@ -471,7 +475,12 @@ export default function workflowControls(pi: ExtensionAPI) {
471
475
 
472
476
  const publishJobStatus = () => {
473
477
  try {
474
- jobsContext?.ui.setStatus(BACKGROUND_STATUS, statusText(jobs?.list() ?? []));
478
+ const all = jobs?.list() ?? [];
479
+ jobsContext?.ui.setStatus(BACKGROUND_STATUS, statusText(all));
480
+ // Chat's jobs panel. A terminal would draw a widget as text above the editor, so only RPC gets it.
481
+ if (jobsContext?.mode === "rpc") {
482
+ jobsContext.ui.setWidget(BACKGROUND_WIDGET, all.length > 0 ? [widgetPayload(all)] : undefined);
483
+ }
475
484
  } catch {
476
485
  // A status line is a courtesy; a closed UI must not fail the job.
477
486
  }
@@ -548,6 +557,24 @@ export default function workflowControls(pi: ExtensionAPI) {
548
557
  }
549
558
  });
550
559
 
560
+ // A bash call that would hold the conversation while it waits is refused whenever a background job
561
+ // could run it instead, so the user can keep talking. Guidance alone was not enough: the agent
562
+ // kept polling CI with sleep loops in bash while the background tool sat unused.
563
+ pi.on("tool_call", async (event: any, ctx) => {
564
+ if (event.toolName !== "bash" || ctx.hasUI !== true) {
565
+ return;
566
+ }
567
+
568
+ const active = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
569
+ if (!active.includes(BACKGROUND_TOOL)) {
570
+ return;
571
+ }
572
+
573
+ const why = blockingShellCall(event.input);
574
+
575
+ return why ? { block: true, reason: blockingShellReason(why) } : undefined;
576
+ });
577
+
551
578
  pi.on("tool_call", async (event: any, ctx) => {
552
579
  if (!scope.active || (event.toolName !== "write" && event.toolName !== "edit")) {
553
580
  return;
@@ -1146,7 +1173,10 @@ export default function workflowControls(pi: ExtensionAPI) {
1146
1173
  name: BACKGROUND_TOOL,
1147
1174
  label: "Background",
1148
1175
  description:
1149
- "Start a long-running shell command (an eval, build or server) without blocking the conversation. It returns at once; the exit code and last lines of output arrive later as a message. Use bash for anything quick, or when the next step needs the output. Bash permission rules apply.",
1176
+ "Start a shell command that may take more than a minute or two (an eval, build, server, test suite, or waiting on CI or a deploy) without blocking the conversation. It returns at once; the exit code and last lines of output arrive later as a message, so never poll or sleep waiting for it. Use bash only for quick commands. Bash permission rules apply.",
1177
+ promptGuidelines: [
1178
+ "Use background, not bash, for anything that may take more than a minute or two or that waits on something (CI, deploys, sleep loops); a long bash call stops the user from talking to you until it returns.",
1179
+ ],
1150
1180
  parameters: Type.Object(
1151
1181
  {
1152
1182
  command: Type.String({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specpi",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Scope control and a human-selected harness improvement loop for Pi",
5
5
  "author": "Tanner Middleton",
6
6
  "repository": {