specpi 0.31.0 → 0.32.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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.32.0 - 2026-09-23
4
+
5
+ - 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.
6
+ - 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.
7
+ - 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.
8
+ - Offer `request_capability` only to an interactive session with a requestable group installed. Headless, it could only refuse. Its description is shorter too.
9
+ - 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.
10
+ - 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.
11
+ - 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.
12
+ - 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.
13
+ - 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.
14
+ - 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.
15
+
3
16
  ## 0.31.0 - 2026-09-22
4
17
 
5
18
  - 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.32.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.
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.32.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
 
@@ -44,7 +44,7 @@ Everything it touches is written down, versioned, and easy to undo.
44
44
 
45
45
  ## Measured context
46
46
 
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.
47
+ 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
48
 
49
49
  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
50
 
@@ -52,7 +52,7 @@ The solid rows are measured by us, from the request each setup actually sends th
52
52
  <a href="https://tannermidd.github.io/SpecPi/research/#specpi">
53
53
  <picture>
54
54
  <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.">
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 11,119, OpenCode 31,043, DeepSeek Harness 31,743, SpecPi enabled 35,767, Codex CLI 41,616, Oh My Pi 66,708, Claude Code 90,460.">
56
56
  </picture>
57
57
  </a>
58
58
  </p>
@@ -64,7 +64,7 @@ The gap between the two SpecPi bars comes from a few separate switches, so the e
64
64
  <a href="https://tannermidd.github.io/SpecPi/research/#specpi">
65
65
  <picture>
66
66
  <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.">
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 1,601, Capability request 995, 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
68
  </picture>
69
69
  </a>
70
70
  </p>
package/SECURITY_MODEL.md CHANGED
@@ -10,7 +10,7 @@ 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
 
@@ -62,7 +62,7 @@ As of 0.3.0 the guard resolves its key the way the advisor does and the way Pi r
62
62
 
63
63
  ## Improvement authority and evidence
64
64
 
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.
65
+ 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
66
 
67
67
  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
68
 
@@ -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",
@@ -124,6 +124,23 @@ export function capabilityInstalled(allToolNames, capability) {
124
124
  return capability.tools.some((name) => known.has(name));
125
125
  }
126
126
 
127
+ /** The tool the model uses to ask for a withdrawn group. */
128
+ export const CAPABILITY_REQUEST_TOOL = "request_capability";
129
+
130
+ /** Whether any requestable group is installed, i.e. whether asking for one could ever succeed. */
131
+ export function anyCapabilityInstalled(allToolNames) {
132
+ return CAPABILITY_NAMES.some((id) => capabilityInstalled(allToolNames, CAPABILITIES[id]));
133
+ }
134
+
135
+ /**
136
+ * Whether to offer the request tool at all. It can only ever ask a human, so a session without one
137
+ * would carry its schema for a tool that always refuses, and a session with no requestable group
138
+ * installed has nothing to ask for.
139
+ */
140
+ export function capabilityRequestOffered({ interactive, allToolNames }) {
141
+ return interactive === true && anyCapabilityInstalled(allToolNames);
142
+ }
143
+
127
144
  /** One catalogue line per capability, for the tool description and its error results. */
128
145
  export function describeCapabilities() {
129
146
  return CAPABILITY_NAMES.map((id) => `${id}: ${CAPABILITIES[id].summary}`).join("; ");
@@ -5,8 +5,10 @@ import { truncateToWidth } from "@earendil-works/pi-tui";
5
5
  import { Type } from "typebox";
6
6
  import {
7
7
  CAPABILITY_NAMES,
8
+ CAPABILITY_REQUEST_TOOL,
8
9
  capabilityActive,
9
10
  capabilityInstalled,
11
+ capabilityRequestOffered,
10
12
  describeCapabilities,
11
13
  findCapability,
12
14
  missingTools,
@@ -389,6 +391,17 @@ export default function workflowControls(pi: ExtensionAPI) {
389
391
  );
390
392
  });
391
393
 
394
+ // Decided once per session, before the first request, so the cached prompt prefix is never
395
+ // disturbed. See capabilityRequestOffered for why a session may not need the tool at all.
396
+ const applyCapabilityRequest = (ctx: ExtensionContext) => {
397
+ const allToolNames = typeof pi.getAllTools === "function" ? pi.getAllTools().map((tool) => tool.name) : [];
398
+ syncToolGroup(
399
+ pi,
400
+ [CAPABILITY_REQUEST_TOOL],
401
+ capabilityRequestOffered({ interactive: ctx.hasUI, allToolNames }),
402
+ );
403
+ };
404
+
392
405
  // Web access ships hidden. A missing or unreadable preference means off, and the
393
406
  // gate only ever touches its own four tool names.
394
407
  let webAccessEnabled = loadStartupActivation();
@@ -397,6 +410,7 @@ export default function workflowControls(pi: ExtensionAPI) {
397
410
  pi.on("session_start", (_event, ctx) => {
398
411
  webAccessEnabled = loadStartupActivation();
399
412
  applyWebAccess();
413
+ applyCapabilityRequest(ctx);
400
414
  beginRestore(ctx);
401
415
  });
402
416
 
@@ -561,7 +575,7 @@ export default function workflowControls(pi: ExtensionAPI) {
561
575
  if (scope.active) {
562
576
  const taskStale = scope.taskDigest !== undefined && currentTask?.digest !== scope.taskDigest;
563
577
  guidance.push(
564
- `[SPECPI SCOPE]\nDeclared paths: ${scope.entries.map((item) => `${sanitizePathLabel(item.path)}${item.directory ? "/" : ""}`).join(", ")}\nPending outside-scope paths: ${scope.pending.map(sanitizePathLabel).join(", ") || "none"}. Do not describe pending paths as accepted scope.${taskStale ? "\nTask-bound scope is stale; do not widen it automatically." : ""}`,
578
+ `[SPECPI SCOPE]\nDeclared paths: ${scope.entries.map((item) => `${sanitizePathLabel(item.path)}${item.directory ? "/" : ""}`).join(", ")}\nPending outside-scope paths: ${scope.pending.map(sanitizePathLabel).join(", ") || "none"}. Keep outside-scope findings pending until the human allows once, acknowledges them with /scope accept, expands scope with /scope add, or clears it. Acknowledgement does not widen scope, and pending paths are not accepted scope.${taskStale ? "\nTask-bound scope is stale; do not widen it automatically." : ""}`,
565
579
  );
566
580
  }
567
581
 
@@ -905,9 +919,9 @@ export default function workflowControls(pi: ExtensionAPI) {
905
919
  };
906
920
 
907
921
  pi.registerTool({
908
- name: "request_capability",
922
+ name: CAPABILITY_REQUEST_TOOL,
909
923
  label: "Request Capability",
910
- description: `Ask the user to restore a withdrawn SpecPi tool group for this session. Available groups — ${describeCapabilities()}. Their tools are hidden to keep each request small, and restoring one mid-session also discards the provider's cached prompt prefix, which measured about 20% of a mid-length attempt's cost in this project's own testing — so request a group only when the current task actually needs it, and continue without it if the user declines. The restored tools are usable from your next message and stay available until the session ends. Delegation is not requestable here; ask the user to run /delegate on.`,
924
+ description: `Ask the user to restore a withdrawn tool group for this session — ${describeCapabilities()}. Hidden tools cannot be called until restored. Ask only when the task needs one: restoring mid-session discards the cached prompt prefix, about 20% of a mid-length task's cost. Restored tools work from your next message; if the user declines, continue without them. Delegation is not requestable; ask the user to run /delegate on.`,
911
925
  parameters: Type.Object(
912
926
  {
913
927
  capability: StringEnum(CAPABILITY_NAMES, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specpi",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Scope control and a human-selected harness improvement loop for Pi",
5
5
  "author": "Tanner Middleton",
6
6
  "repository": {
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: specpi-improve
3
3
  description: Turns a selected SpecPi capability-gap wishlist item into a minimal, evidence-backed, reversible improvement. Use when the user asks to improve SpecPi from its wishlist, act on a selected gap, or complete the notice-to-retire loop.
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # SpecPi Improve
@@ -2,11 +2,7 @@
2
2
 
3
3
  - Keep changes small, testable, reversible, and tied to the human's request. Inspect existing code and guidance before editing; avoid adjacent feature accumulation.
4
4
  - Prefer direct work and existing project tools. Ask concise questions only when material requirements are unclear.
5
- - Keep replies brief and human-readable. Lead with the answer, use plain language, and avoid jargon, technical babble, and repetitive progress updates. Include technical detail only when it helps the human decide or act.
6
- - Write commits and pull requests the same way: a short, clear title and a brief explanation of what changed, why, and what was tested. Avoid boilerplate, inflated claims, and unnecessary implementation detail.
7
- - When `/scope` is active, keep outside-scope findings pending until the human allows once, acknowledges them with `/scope accept`, expands scope with `/scope add`, or clears it. Acknowledgement does not widen scope.
8
- - Treat wishlist observations as leads, not authorization. Start a wishlist-sourced change only from an exact `/harness-improvement` selection and follow `specpi-improve`. Record its contract before editing and retire only after its verification gate passes.
9
- - Web access and Browser QA tools ship hidden to keep each request small. When the task genuinely needs one, call `request_capability`; it asks the human, who may decline. Continue without the capability if they do, and do not attempt a hidden tool. The human can also run `/webaccess on` or `/browser on` directly. Delegation is not requestable this way; ask the human to run `/delegate on`.
10
- - Never inspect Pi authentication, provider credentials, trust decisions, sessions, missions, or history to improve the harness. Use only the active extension context and intended local improvement records.
5
+ - Keep replies brief and human-readable: lead with the answer, use plain language, avoid jargon and repetitive progress updates, and include technical detail only when it helps the human decide or act.
6
+ - Write commits and pull requests the same way: a short, clear title and a brief note on what changed, why, and what was tested.
11
7
  - Use observed files, diffs, tests, and runtime behavior as evidence. Run relevant checks, inspect the final diff, and obtain fresh read-only review when risk warrants it. Report results and residual risks without claiming more than the checks prove.
12
8
  - Do not commit, push, publish, deploy, or alter remote state unless explicitly requested.