specpi 0.31.0 → 0.33.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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.33.0 - 2026-09-23
4
+
5
+ - 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.
6
+ - Keep it one small tool that only starts jobs, 657 characters of schema. Status needs no tool, because the result arrives on its own, and stopping is a human command. Jobs run through Pi's own shell runner with the session's shell settings, are limited to four at once, log to capped owner-only files, and end with the session, which deletes their logs.
7
+ - Apply every `bash` permission rule to background jobs. The installer records `background` in the permission system's `shellTools`, which gates its command through the same stack as `bash`, and removes the entry at uninstall. Before every start the extension checks the effective mapping, including a trusted project's, and refuses without it.
8
+ - Turn background jobs off while the Jev command guard is on, because the guard only checks `bash`. Its saved state is read the way the guard reads it. A guard switched on for one session only is invisible to other extensions, so that case is documented rather than guessed at.
9
+ - Offer the tool only to interactive sessions where a start could succeed, decided before the first request. Eval runs never see it, so their tool surface is unchanged.
10
+ - Pair with SpecPi Chat 0.16.0, which shows running jobs in the session footer.
11
+ - Say plainly in the working agreement that irreversible steps nobody asked for are off the table: rewriting Git history, pruning reflogs or objects, force-pushing, deleting data that cannot be restored. The agent does the reversible part, then says what remains and offers it. On Terminal-Bench's `sanitize-git-repo`, every attempt from either arm that left Git history alone passed, and every failure had rewritten it, destroying the commit the checker compares against. SpecPi's "reversible" wording already halved how often that happened (9 of 18 attempts against Pi's 17 of 18), which is why it led that task 12/18 to 3/18. The explicit rule targets the remaining 9. It is also the better behaviour outside a benchmark: purging keys from history breaks existing clones and needs a force-push, so it is a decision to offer, not to take. It adds 233 characters to every request, and its effect on the benchmark is not yet measured.
12
+
13
+ ## 0.32.0 - 2026-09-23
14
+
15
+ - Send less on every request. SpecPi's first request was 14,022 characters against Pi's 5,521, and that overhead rides every turn and is never removed by compaction. Measured again with all eight pins, the interactive default is now 11,119. A headless session drops the two tools below as well, which the measured parts put at about 8,300: SpecPi's overhead falls from 8,501 characters to about 2,800.
16
+ - Trim the installed working agreement from about 2,400 characters to 1,300, keeping the rules that shape ordinary work, including the restraint that wins `sanitize-git-repo` on Terminal-Bench. The `/scope` rule moves into the scope notice SpecPi already adds while scope is active. The wishlist and credential rules already live in the improvement skill, and the web and browser rule now lives in `request_capability`'s own description.
17
+ - Offer `report_capability_gap` only where a report could land: collection on, or undecided in an interactive session, where its first report asks for consent. With collection off, or undecided in a headless session, it could only ever answer "Not recorded". `/wishlist on` and `/wishlist off` switch it with collection. Its four guidance lines, which repeated its description, are now one.
18
+ - Offer `request_capability` only to an interactive session with a requestable group installed. Headless, it could only refuse. Its description is shorter too.
19
+ - Keep the improvement skill out of the model's skill list. It only ever runs from a `/harness-improvement` selection, so the kickoff now names its file, and Pi still lists it as a command.
20
+ - Repair `scripts/measure-context.mjs`, which failed on 0.31.0: it expected two first-party extensions and authoring tools that no longer ship by default. It now records where each Pi row's instruction characters go, section by section. The published record and charts are re-measured; Oh My Pi runs again (18.2.5), so its row is no longer carried forward.
21
+ - Give tier 6's Pi-family harnesses compaction settings that fit the declared window. Pi's defaults compact at 7,616 tokens of a 24,000 window and try to keep 20,000, so earlier runs compacted almost every turn; plain SpecPi compacted 17-19 times on the marathon task against Pi's 5-7, because its fixed prompt took most of what was left. Runs from before this setting are not comparable.
22
+ - Note on the evaluations page that `configure-git-webserver` measures a cleanup habit. Its checker's push never succeeded for any harness, so every pass came from a test file left in the web root. That also qualifies a claim in 0.31.0: SpecPi + Jev's self-stopped failures outnumber Pi's only because of this task. Without it they are level, 31 against 32.
23
+ - Consider a background-task system and do not build one. In the Terminal-Bench transcripts agents already start servers with `nohup` or `&`, tool timeouts are about one in eighteen attempts, and the one task that spends minutes blocked in commands (`tune-mjcf`) times out equally in both arms. Both earlier background-task integrations were removed, and a new one would add tool schema to every request.
24
+ - SpecPi Chat 0.15.0 needs no change: it runs Pi interactively, so both tools stay available to it, and its command menu still lists the improvement skill.
25
+
3
26
  ## 0.31.0 - 2026-09-22
4
27
 
5
28
  - Report Terminal-Bench 2.0 per sitting rather than per run. Bare Pi -- unchanged software, the same thirteen tasks, the same machine -- solved 21, 22, 30, 30 and 32 of 39 across five launches, so a figure from one run is not evidence about a harness. The page pools every sitting, publishes each harness's per-sitting range beside its rate, and charts the spread directly.
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.31.0**, paired with the Chat **0.15.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.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.
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.31.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.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.
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
 
@@ -38,13 +38,14 @@ It focuses on five things:
38
38
  - **Accuracy:** exact version pins, checksums on state, rollback on failure, and proof over promises
39
39
  - **Improvement:** local notes become small, checked changes through `/harness-improvement`
40
40
  - **Efficiency:** delegation, persistent goals, and browser QA handled by the right tool for the job
41
+ - **Keep talking:** long evals, builds and servers can run as background jobs, so the conversation is not stuck until they finish. The agent hears back when each one ends, and `/jobs` lists or stops them
41
42
  - **Lean default:** web access, browser QA, and delegation stay off until you need them. Turn them on for a session with `/webaccess on`, `/browser on`, and `/delegate on` — or let the agent ask when it hits the need, and answer the prompt
42
43
 
43
44
  Everything it touches is written down, versioned, and easy to undo.
44
45
 
45
46
  ## Measured context
46
47
 
47
- This chart shows first-call context from a clean install: all eight pinned packages, the working agreement, and the skills Pi finds. It has not been re-measured since the command guard returned to a pinned package rather than a native system. "Enabled" means browser QA, delegation, and web access are switched on, with no goal, scope, or improvement selection active.
48
+ This chart shows first-call context from a clean install: all eight pinned packages, the working agreement, and the skills Pi finds, in an interactive session with wishlist collection undecided. A headless session, or one with collection off, also leaves out the gap report and capability request tools. "Enabled" means browser QA, delegation, and web access are switched on, with no goal, scope, or improvement selection active.
48
49
 
49
50
  The solid rows are measured by us, from the request each setup actually sends through one local test provider. That includes OpenCode, the DeepSeek Harness, and Oh My Pi, all measured as installed. The faded Codex CLI and Claude Code rows come from HarnessTax's published numbers, measured under their own setup. Treat those as a rough reference, not a head-to-head test. These are character counts. They say nothing about tokens, cost, or how well each tool does the job. The research page [breaks down the enabled setup by feature](https://tannermidd.github.io/SpecPi/research/#specpi), so you can see what each switch costs on its own.
50
51
 
@@ -52,7 +53,7 @@ The solid rows are measured by us, from the request each setup actually sends th
52
53
  <a href="https://tannermidd.github.io/SpecPi/research/#specpi">
53
54
  <picture>
54
55
  <source media="(prefers-color-scheme: dark)" srcset="https://tannermidd.github.io/SpecPi/media/context-chart-dark.svg">
55
- <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 15,069, OpenCode 31,043, DeepSeek Harness 31,743, SpecPi enabled 40,203, Codex CLI 41,616, Oh My Pi 65,816, 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,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
57
  </picture>
57
58
  </a>
58
59
  </p>
@@ -64,7 +65,7 @@ The gap between the two SpecPi bars comes from a few separate switches, so the e
64
65
  <a href="https://tannermidd.github.io/SpecPi/research/#specpi">
65
66
  <picture>
66
67
  <source media="(prefers-color-scheme: dark)" srcset="https://tannermidd.github.io/SpecPi/media/capability-chart-dark.svg">
67
- <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 4,214, Goals 1,315, Browser QA 8,046, 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 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
69
  </picture>
69
70
  </a>
70
71
  </p>
package/SECURITY_MODEL.md CHANGED
@@ -10,10 +10,22 @@ Scope records use Pi's current session branch. Restoring a branch does not creat
10
10
 
11
11
  ## Capability requests
12
12
 
13
- Optional tool groups ship withdrawn. The `request_capability` tool lets the model name a withdrawn group instead of silently working around it; it never grants one. Activation requires a human decision, either accepted in the moment or recorded earlier as a standing grant, so the tool refuses in headless sessions and a declined prompt leaves the session unchanged. Activation is additive, offers only the named group's tools, and lasts for the session: it writes no startup preference, and the next session starts from the saved preference as before. Delegation is not requestable, because its own package requires a human command to bind a model. Acceptance offers the tools; it is not a per-call permission, and the Permission System continues to govern what those tools may do.
13
+ Optional tool groups ship withdrawn. The `request_capability` tool lets the model name a withdrawn group instead of silently working around it; it never grants one. Activation requires a human decision, either accepted in the moment or recorded earlier as a standing grant, so the tool is not offered in headless sessions or when no requestable group is installed, refuses if called without an interactive user, and a declined prompt leaves the session unchanged. Activation is additive, offers only the named group's tools, and lasts for the session: it writes no startup preference, and the next session starts from the saved preference as before. Delegation is not requestable, because its own package requires a human command to bind a model. Acceptance offers the tools; it is not a per-call permission, and the Permission System continues to govern what those tools may do.
14
14
 
15
15
  `/capability allow <name>` records a standing grant so that group is offered without a prompt, and `/capability ask <name>` returns it to prompting. Recording either requires an interactive human command, and a grant that fires still announces itself in the session. Grants live in `<agent-dir>/specpi/capabilities/settings.json`, written atomically with owner-only permissions; links and irregular files are refused, and an unreadable file, a foreign schema or an unknown capability name reads as no grant. A standing grant removes the prompt, not the requirement for a human: headless sessions are still refused, so a granted capability cannot be activated by an unattended run. It also does not offer the group at startup — the model must still ask, and the group stays withdrawn until it does.
16
16
 
17
+ ## Background jobs
18
+
19
+ The `background` tool starts a shell command and returns at once; the job reports its exit code and last lines of output as a follow-up message when it ends. It runs through Pi's own local shell runner with the session's `shellPath` and `shellCommandPrefix`, so the shell, working directory and process-tree cleanup on Pi's exit match the `bash` tool. It adds no new authority, and every start fails closed:
20
+
21
+ - **Permission parity.** The installer records `shellTools.background = { commandArgument: "command" }` in the permission system's global config, so its full `bash` stack — command decomposition, wrapper flooring, path and outside-directory checks, and every `bash:` rule — applies to the command. The entry is merged into the existing file, backed up and rolled back with the rest of the transaction, left alone when the file carries comments, and removed at uninstall only if it is still SpecPi's. Before every start the extension reads the effective mapping (global, then a trusted project's, which could remap it) and refuses unless it gates `command`. An unreadable config refuses too.
22
+ - **Command guard.** `specpi-jev-guard` checks `bash`, `powershell`, `write` and `edit` only, so background jobs are off while it is on. The extension resolves the guard's saved state as the guard does — its default on, the global file, then a trusted project's — and starts a job only when that resolves off. The guard's session-only switch lives in its own memory and no other extension can read it, so a guard switched on with `/jev-guard on` without `--global`, over a saved off, does not stop background jobs. Save the choice with `--global` to cover them.
23
+ - **Interactive only.** Headless sessions are not offered the tool and are refused if they call it; there is nobody to report back to. A Pi that cannot list its commands is refused, because the guard and permission system cannot be detected.
24
+ - **Bounded.** At most four jobs run at once. Each log is capped at 8 MiB under `<agent-dir>/specpi/background/`, owner-only, and deleted when the session ends; directories left by a crashed session are pruned after a day. A completion message carries at most 40 lines and 4,000 characters of output with control characters removed. Output is untrusted text, like any tool result.
25
+ - **Session-owned.** `/jobs stop` and session end kill each job's process tree; Pi's own exit handling also kills tracked children. Process cleanup is best effort: a program that detaches itself from its parent, or a daemon a job starts, can outlive it, exactly as with `bash`.
26
+
27
+ Eval runs overwrite the permission config with their `yoloMode` opt-in, which drops the mapping, so the tool is never offered there and eval rows keep their measured tool surface.
28
+
17
29
  ## Jev advisor
18
30
 
19
31
  The advisor is the only part of SpecPi that sends anything off this machine, and it ships off. With the master switch off there is no network call, no key read, no consent read and no prompt injection: the harness behaves exactly as it did before the extension existed.
@@ -62,7 +74,7 @@ As of 0.3.0 the guard resolves its key the way the advisor does and the way Pi r
62
74
 
63
75
  ## Improvement authority and evidence
64
76
 
65
- Collection is off by default. Enabling it permits sanitized gap observations, not implementation. Only an exact human `/harness-improvement` selection authorizes a wishlist-sourced change. The selected contract is bound to the gap, source checkout, session, and selection generation. Web access tools (`web_search`, `source_check`, `fetch_content`, `get_search_content`) are hidden until a human offers them, through `/webaccess on` or by accepting a `request_capability` prompt.
77
+ Collection is off by default. Enabling it permits sanitized gap observations, not implementation. The observation tool is offered only where a report could land: collection on, or undecided in an interactive session, where its first report asks for consent. The improvement skill is kept out of the model's skill list and is named by path only in the `/harness-improvement` kickoff. Only an exact human `/harness-improvement` selection authorizes a wishlist-sourced change. The selected contract is bound to the gap, source checkout, session, and selection generation. Web access tools (`web_search`, `source_check`, `fetch_content`, `get_search_content`) are hidden until a human offers them, through `/webaccess on` or by accepting a `request_capability` prompt.
66
78
 
67
79
  Retirement requires source registry integration, unchanged verification policy, a matching contract, bounded source snapshots, `npm run check`, and closed registered validators. Receipts distinguish machine-observed gates from model-reported acceptance evidence. Stale selections, changed source, missing evidence, and failed checks reject retirement. A validator proves only the behavior it exercises; the human remains responsible for accepting the result. The loop never commits, publishes, or installs a resulting change automatically.
68
80
 
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.15.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.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.
@@ -7,36 +7,66 @@
7
7
  // the answer is a boolean — no latency, no cost, no false positives, and nothing for a classifier
8
8
  // to route.
9
9
  //
10
- // `report_capability_gap` is deliberately not in this list. It is the *observation* tool and the
11
- // reason the improvement loop exists; withdrawing it would silently lose the friction reports the
12
- // loop is built to capture. It stays offered at all times.
10
+ // `report_capability_gap` is the *observation* tool and the reason the improvement loop exists, so
11
+ // it stays offered whenever a report could land: collection on, or undecided with a human present to
12
+ // answer the consent prompt its first call raises. Only when nothing can be recorded is it withdrawn
13
+ // -- collection switched off, or undecided in a headless session that can never ask. Its schema and
14
+ // guidance were about 2.5 KB of every request, paid in exactly the sessions where it always answered
15
+ // "Not recorded".
16
+
17
+ import { fileURLToPath } from "node:url";
13
18
 
14
19
  /** Only usable while an improvement is selected. */
15
20
  export const AUTHORING_TOOL_NAMES = Object.freeze(["record_harness_contract", "finish_harness_improvement"]);
16
21
 
22
+ /** The observation tool. */
23
+ export const OBSERVATION_TOOL_NAME = "report_capability_gap";
24
+
25
+ /**
26
+ * The improvement skill is hidden from the model's skill list (`disable-model-invocation`), because it
27
+ * only ever runs from a /harness-improvement selection. Installed resources and the npm package share
28
+ * the same layout, so the path is resolved from this file rather than from the agent directory.
29
+ */
30
+ export function improvementSkillPath() {
31
+ return fileURLToPath(new URL("../../skills/specpi-improve/SKILL.md", import.meta.url));
32
+ }
33
+
34
+ /** Whether a report could be recorded in this session, which is the only reason to offer the tool. */
35
+ export function observationToolWanted(mode, interactive) {
36
+ return mode === "on" || (mode === "undecided" && interactive === true);
37
+ }
38
+
17
39
  /**
18
- * Add or remove the authoring tools without disturbing any other extension's tools. Mirrors the
40
+ * Add or remove named tools without disturbing any other extension's tools. Mirrors the
19
41
  * web-access gate: it only ever touches the names it owns, and it does nothing when the active set
20
42
  * already matches, so a no-op never costs the cached prompt prefix.
21
43
  */
22
- export function syncAuthoringTools(pi, selected) {
44
+ function syncOwnedTools(pi, names, wanted) {
23
45
  if (typeof pi?.getActiveTools !== "function" || typeof pi?.setActiveTools !== "function") {
24
46
  return false;
25
47
  }
26
48
 
27
- const owned = new Set(AUTHORING_TOOL_NAMES);
49
+ const owned = new Set(names);
28
50
  const active = pi.getActiveTools();
29
51
  const present = active.filter((name) => owned.has(name));
30
- if (selected && present.length === owned.size) {
52
+ if (wanted && present.length === owned.size) {
31
53
  return false;
32
54
  }
33
55
 
34
- if (!selected && present.length === 0) {
56
+ if (!wanted && present.length === 0) {
35
57
  return false;
36
58
  }
37
59
 
38
60
  const others = active.filter((name) => !owned.has(name));
39
- pi.setActiveTools(selected ? [...others, ...AUTHORING_TOOL_NAMES] : others);
61
+ pi.setActiveTools(wanted ? [...others, ...names] : others);
40
62
 
41
63
  return true;
42
64
  }
65
+
66
+ export function syncAuthoringTools(pi, selected) {
67
+ return syncOwnedTools(pi, AUTHORING_TOOL_NAMES, selected);
68
+ }
69
+
70
+ export function syncObservationTool(pi, wanted) {
71
+ return syncOwnedTools(pi, [OBSERVATION_TOOL_NAME], wanted);
72
+ }
@@ -13,7 +13,12 @@ import { getMarkdownTheme, type ExtensionAPI } from "@earendil-works/pi-coding-a
13
13
  import { StringEnum } from "@earendil-works/pi-ai";
14
14
  import { Box, Markdown, Text } from "@earendil-works/pi-tui";
15
15
  import { Type } from "typebox";
16
- import { syncAuthoringTools } from "./authoring-tools.mjs";
16
+ import {
17
+ improvementSkillPath,
18
+ observationToolWanted,
19
+ syncAuthoringTools,
20
+ syncObservationTool,
21
+ } from "./authoring-tools.mjs";
17
22
  import {
18
23
  appendWishlistDecision,
19
24
  archiveWishlist,
@@ -468,7 +473,7 @@ function improvementPrompt(group: any, context: ImprovementContext = {}) {
468
473
 
469
474
  lines.push(
470
475
  "",
471
- "This exact menu selection authorizes implementation of the smallest sufficient intervention in the current SpecPi source checkout. Load and follow the specpi-improve skill. Treat the wishlist evidence as a lead, inspect current behavior, keep scope minimal, and do not ask for another approval unless scope expands or external/remote state would change.",
476
+ `This exact menu selection authorizes implementation of the smallest sufficient intervention in the current SpecPi source checkout. Read and follow the specpi-improve skill at ${improvementSkillPath()}. Treat the wishlist evidence as a lead, inspect current behavior, keep scope minimal, and do not ask for another approval unless scope expands or external/remote state would change.`,
472
477
  "First call record_harness_contract with the exact gap, selection ID, source root, proposed requirements, acceptance checks, paths, rollback, and non-goals. Run direct acceptance checks and focused tests. At the end, call finish_harness_improvement with the gap ID, concise acceptance evidence, and a validation note. That tool independently runs the repository gate, verifies registry integration and frozen policy inputs, runs supported capability validators, records a bounded local receipt, and retires the item. If any check fails, do not retire it; leave it selected and report the blocker.",
473
478
  );
474
479
 
@@ -586,9 +591,23 @@ export default function toolWishlist(pi: ExtensionAPI) {
586
591
  }
587
592
  };
588
593
 
594
+ // Offer the observation tool only where a report could land. Decided at session start and on an
595
+ // explicit /wishlist on|off, never in between, so the cached prompt prefix is not disturbed.
596
+ const syncObservation = (interactive: boolean) => {
597
+ let mode = "off";
598
+ try {
599
+ mode = readCollectionMode(stateDir);
600
+ } catch {
601
+ // An unreadable collection file cannot record anything either.
602
+ }
603
+
604
+ syncObservationTool(pi, observationToolWanted(mode, interactive));
605
+ };
606
+
589
607
  pi.on("session_start", (_event, ctx) => {
590
608
  restoreActiveImprovement(ctx);
591
609
  syncAuthoring();
610
+ syncObservation(ctx.hasUI === true);
592
611
  });
593
612
 
594
613
  pi.on("session_tree", (_event, ctx) => {
@@ -653,13 +672,10 @@ export default function toolWishlist(pi: ExtensionAPI) {
653
672
  name: "report_capability_gap",
654
673
  label: "Report Capability Gap",
655
674
  description:
656
- "Privately record a material, reusable capability gap in SpecPi's local wishlist. Report only after reasonable existing tools or workarounds proved insufficient. Do not use for transient failures, command mistakes, credentials or permissions the user must supply, ordinary project-specific work, or speculative nice-to-haves. Never include secrets, source code, full commands, file paths, URLs with private data, or user prompt text. Report a gap at most once per user task. Collection requires an explicit local on/off decision. Only a separately enabled and consented Jev advisor may transmit redacted report samples.",
675
+ "Privately record, in SpecPi's local wishlist, a material and reusable capability gap that reasonable existing tools or workarounds could not cover. Not for transient failures, command mistakes, credentials or permissions the user must supply, ordinary project work, or speculative nice-to-haves. At most once per gap per task. Never include secrets, source code, full commands, file paths, private URLs, or user prompt text.",
657
676
  promptSnippet: "Record recurring, generalizable capability friction without interrupting the user task",
658
677
  promptGuidelines: [
659
- "Use report_capability_gap only for a material and generalizable missing capability after reasonable existing tools or workarounds have proved insufficient.",
660
- "Do not use report_capability_gap for transient errors, model mistakes, missing credentials or permissions, ordinary project-specific work, or speculative nice-to-haves.",
661
- "Call report_capability_gap at most once per distinct gap per user task; use a short durable capability phrase without project names, and never include secrets, source code, full commands, private paths, or user prompt text.",
662
- "After report_capability_gap, continue the requested task; never treat a report as permission to modify SpecPi or external state.",
678
+ "After report_capability_gap, continue the requested task; a report never authorizes changing SpecPi or external state.",
663
679
  ],
664
680
  parameters: Type.Object(
665
681
  {
@@ -1450,6 +1466,8 @@ export default function toolWishlist(pi: ExtensionAPI) {
1450
1466
 
1451
1467
  if (action === "on" || action === "off") {
1452
1468
  await setCollectionMode({ stateDir, mode: action });
1469
+ // A human command, so offering or withdrawing the tool now is the expected cost.
1470
+ syncObservation(ctx.hasUI === true);
1453
1471
  ctx.ui.notify(
1454
1472
  `Local wishlist collection is ${action}. Jev transmission requires its own switch and consent.`,
1455
1473
  "info",