pi-supernova 0.4.0 → 0.6.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/README.md CHANGED
@@ -12,6 +12,20 @@ Ordinary JavaScript control flow remains available; the guest command bindings
12
12
  are only `read`, `edit`, `write`, and `bash`. Supernova supplies retrieval,
13
13
  transactional file operations, batching, bounded results and the grouped nova UI.
14
14
 
15
+ ## What is new in 0.6.0
16
+
17
+ - **Shared batch input:** supply top-level `data` once; each program gets an
18
+ independent copy unless it supplies its own replacement data.
19
+ - **Conflict protection:** byte snapshots survive partial reads and body-cache
20
+ eviction; receipt generation cannot silently rebase a pending write.
21
+ - **Read fidelity:** staged declarations remain discoverable in large/new files,
22
+ line windows preserve source endings, and `complete` always means the whole file.
23
+ - **Explicit failures:** incompatible read modes, budget-limited matches, captured
24
+ overrides and conflicting new-file aliases no longer silently change outcomes.
25
+
26
+ See the [changelog](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/CHANGELOG.md)
27
+ and [token measurements](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md).
28
+
15
29
  ## Install and update
16
30
 
17
31
  Install the published package in your host:
@@ -29,9 +43,19 @@ pi install /path/to/pi-stack/packages/pi-supernova
29
43
 
30
44
  Git pushes do not update npm installations. Publish the new npm version first;
31
45
  then reinstall it in the host. Reinstall explicitly when an existing version
32
- range excludes the new minor version (for example, `^0.2.0` excludes `0.3.0`).
46
+ range excludes the new minor version (`^0.5.0` excludes `0.6.0`). After 0.6.0 is
47
+ published, pin that release with:
48
+
49
+ ```bash
50
+ pi install npm:pi-supernova@0.6.0
51
+ omp install npm:pi-supernova@0.6.0
52
+ ```
33
53
 
34
- Both package manifests use `index.js`. The old `src/bridge/pi-extension.ts` path
54
+ In Pi, `pi list` shows the configured package sources. A local path uses that
55
+ checkout directly; an npm source uses the installed npm copy. Do not assume that
56
+ pushing a checkout or running `/reload` updates the copy executing in your host.
57
+
58
+ Both host manifests use `index.js`. The old `src/bridge/pi-extension.ts` path
35
59
  remains a compatibility entrypoint but no longer imports Pi tool factories.
36
60
  After updating JavaScript sources, fully exit Pi and resume in a new process.
37
61
  Pi 0.85.1 can retain native ESM modules across `/reload`, even after its extension
@@ -50,12 +74,13 @@ settings. The runtime does not silently rewrite your tool policy.
50
74
  | `read` | `read(path, offset?, limit?)`, `read({path,offset,limit})`; one-based line windows |
51
75
  | `read` | `return await read("plot.png")`; displays images directly, without a browser |
52
76
  | `read` | `read({path,json:".verdict"})`; parse full JSON before bounded field selection |
53
- | `read` | `read(directory)`, `read("symbol or question")`, `read(path,{about:question})`; questions locate and open source directly |
54
- | `read` | `read({query,resolve:true})`; structured source and status for a resolve-to-edit handoff |
77
+ | `read` | `read(directory)`, `read("symbol or question")`, `read(path,{about:question})`; a path is raw text, a symbol is a view |
78
+ | `read` | `read({query,resolve:true})`; same view as `read("symbol")`: status, path, line, lines, text, complete |
55
79
  | `read` | `read({query,evidence:true})`; ranked evidence with provenance; optional `path` scopes discovery |
56
80
  | `read` | `read({path,outline:true})`; structural declarations |
57
- | `read` | `read([path1,path2])`; up to 64 paths, ordered values with labelled individual failures |
58
- | `edit` | `edit(path,oldText,newText)`, `edit({path,edits:[{oldText,newText}]})`; related edits validated against one original file |
81
+ | `read` | `read([path1,path2])`; up to 64 paths, ordered values; rejects if any path fails |
82
+ | `edit` | `edit(path,oldText,newText)`, `edit({path,edits:[{oldText,newText}]})`; unique in the file |
83
+ | `edit` | `edit(view,text)` CAS-replaces that span; `edit(view,old,new)` is unique inside it |
59
84
  | `edit` | `edit({path,patch})`; unified patch application |
60
85
  | `edit` | `edit(async () => {...})`; filesystem-only checkpoint, described below |
61
86
  | `write` | `write(path,text)`, `write({path,content})`; atomic replacement |
@@ -70,27 +95,33 @@ payloads are not repeated in owned direct-execution errors;
70
95
  stdout/stderr, exit status and source context remain. Session environment variables are taken
71
96
  from the current execution context, not inherited from a different parent session.
72
97
 
73
- Source questions resolve and open the selected file in one command. An exact
98
+ Source questions locate a declaration in one command. An exact
74
99
  declaration match uses one bounded direct ripgrep search, without a prerequisite
75
100
  file listing, persistent index, embeddings or summarization. A transient filename
76
101
  listing is a fallback for unmatched content or unresolved bare filenames. Natural-language
77
- questions reuse lexical stemming. Ripgrep must be available on PATH.
102
+ questions reuse lexical stemming. Source questions and focused `about` reads
103
+ accept at most 16 keywords. Ripgrep must be available on PATH.
78
104
 
79
- Successful question reads return raw source with a path/range header, not the old
80
- JSON location preview. Use the structured form when code needs the path:
105
+ `read(path)` stays raw text. `read("symbol")` is the same view as
106
+ `read({query, resolve:true})` — not the file, not a path/range header:
81
107
 
82
108
  ```javascript
83
- const source = await read({query: "validateRefreshToken", resolve: true});
84
- if (source.status !== "found") return source;
85
- return await edit(source.path, "token.length > 3", "token.length > 5");
109
+ const v = await read("validateRefreshToken");
110
+ if (v.status !== "found") return v;
111
+ await edit(v, v.text.replace("token.length > 3", "token.length > 5"));
86
112
  ```
87
113
 
88
- The structured result contains `status`, `path`, the matching `line`, delivered
89
- `lines`, unchanged `text`, `complete`, and `nextOffset` when more source follows.
90
- Files that fit the output budget are returned in full. Oversized files open near
91
- the matching line and give a continuation; they are not summarized. Uncertain
114
+ The view contains `status`, `path`, the matching `line`, span `lines`, unchanged
115
+ `text`, `complete`, and `nextOffset` when a budget clip continues. A declaration
116
+ snap is that span (`complete` is false unless the span is the whole file). Uncertain
92
117
  results report `ambiguous`, `not_found` or `incomplete` with no selected path.
93
- Use `{path: directory, about: question, resolve: true}` to narrow the scope.
118
+ Use `{path: directory, about: question}` to narrow the scope. Scoping a query
119
+ does not relabel a selected span as a complete file. Newly staged files and large
120
+ staged source participate in discovery before commit. Raw offset/limit windows
121
+ preserve LF/CRLF endings and the final newline; focused views add line labels.
122
+ A matching window too large for the output budget is reported as budget-limited,
123
+ not as an absent match. Do not combine incompatible modes such as `outline:true`
124
+ and `evidence:true`.
94
125
 
95
126
  Ordinary reads stay self-contained. Outlines and graph evidence remain explicit
96
127
  options, not mandatory stages of source resolution. Ordinary calls also get:
@@ -122,6 +153,14 @@ For intentionally writing literal marker documentation only, opt in with
122
153
  `write({path,content,allowReadArtifacts:true})`. This is a data-loss guard, not
123
154
  full dataflow tracking or a security sandbox.
124
155
 
156
+ Read/modify/write conflict checks retain a signature of the actual disk bytes,
157
+ including for partial and large-file reads. A fresh explicit text read refreshes
158
+ that observation; internal receipt reads and body-cache eviction do not. Commits
159
+ reject changed content and conflicting symlink aliases, including new file paths.
160
+ These checks do not provide a cross-process lock or make shell/import mutations
161
+ transactional. Extensionless filenames also support `complete:true`, for example
162
+ `read({path:"LICENSE",complete:true})`.
163
+
125
164
  Explicit read arrays reject missing/failed paths. For typed partial outcomes use
126
165
  `Promise.allSettled(paths.map(path => read(path)))`. Successful arrays remain arrays.
127
166
  For literal file content or scripts, prefer the optional tool-level `data` parameter:
@@ -136,8 +175,9 @@ For literal file content or scripts, prefer the optional tool-level `data` param
136
175
  }
137
176
  ```
138
177
 
139
- `data` crosses the worker boundary as JSON, never as JavaScript source. Its
140
- JSON-encoded length is capped separately at `maxCodeChars`; split larger inputs.
178
+ `data` crosses the worker boundary as JSON, never as JavaScript source. For a
179
+ single program its JSON-encoded length is capped separately at `maxCodeChars`;
180
+ batches use the combined admission budget described below. Split larger inputs.
141
181
  The binding exists only when supplied, so older programs declaring their own `data`
142
182
  remain valid. Syntax errors run no commands and give quoting guidance. For inline
143
183
  source, use `String.raw` (escaping backtick delimiters) or JSON-quoted strings.
@@ -179,7 +219,7 @@ no truncated prefix is executed. Review untrusted source before running it.
179
219
  Use ordinary `edit` to revise saved programs. This is explicit source reuse, not
180
220
  conversation compression: prior calls and read results remain intact. Creation
181
221
  costs an additional call unless combined with other work, so prefer inline code
182
- for short one-off operations. See [token measurements](docs/TOKEN_COSTS.md).
222
+ for short one-off operations. See [token measurements](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md).
183
223
 
184
224
  ### Batch already-known continuations
185
225
 
@@ -194,12 +234,32 @@ for short one-off operations. See [token measurements](docs/TOKEN_COSTS.md).
194
234
  }
195
235
  ~~~
196
236
 
197
- Use programs instead of top-level code/file/data. Supply 1--32 entries, each with
198
- code OR file and optional data; the JSON-encoded array must fit maxCodeChars.
237
+ Use programs instead of top-level code/file. Supply 1--32 entries, each with
238
+ code OR file and optional data. Top-level data supplies an optional default for
239
+ each entry; explicit entry data replaces it entirely, including null, false, 0
240
+ and empty strings. Every guest receives its own copy, not a shared mutable heap.
241
+ The JSON-encoded array (or `{programs,data}` when defaults are supplied) must fit
242
+ maxCodeChars. Common input counts once; result representations and output limits are unchanged.
199
243
  Entries run sequentially in fresh guests and commit separately. A successful
200
244
  entry can create the file executed by a later entry. No implicit retries,
201
245
  reordering, shared heap or nested batches are introduced.
202
246
 
247
+ For independent audits that use the same inputs, send them once:
248
+
249
+ ```json
250
+ {
251
+ "data": {"paths": ["src/a.js", "src/b.js"]},
252
+ "programs": [
253
+ {"code": "return await read(data.paths);"},
254
+ {"code": "return await Promise.all(data.paths.map(path => read({path, outline:true})));"}
255
+ ]
256
+ }
257
+ ```
258
+
259
+ This avoids repeating literal arguments, without a compression codec or result elision.
260
+ Mutating `data` in one guest cannot affect the next. An entry with `data:null`
261
+ receives null, not the shared object; there is no implicit object merge.
262
+
203
263
  The batch stops on the first failed entry, cancellation/deadline, or exhausted
204
264
  output/log/image budget. Earlier successful commits remain; only the active
205
265
  program's uncommitted writes roll back. Admission errors throw before any program.
@@ -266,6 +326,8 @@ plain .json reads also fail with a projection hint. Explicit offset/limit or
266
326
  resolve:true still allow raw inspection, but line windows are not JSON documents.
267
327
  Do not combine json with complete, line windows, or source views. External read
268
328
  overrides reject JSON projection rather than silently ignoring the option.
329
+ Other read options, even false-valued flags, do not bypass a captured external
330
+ read executor; its policy, transforms and failures remain authoritative.
269
331
 
270
332
  For large Markdown/log path audits, use read(path,{about:"document path"}) or
271
333
  explicit offset/limit, not complete:true. Larger JSON needs a streaming parser via
@@ -424,7 +486,7 @@ not hard real-time guarantees.
424
486
  It excludes model latency, provider tokens and prewarm time; it is not a universal
425
487
  comparison against every CodeMode implementation.
426
488
 
427
- See [the changelog](docs/CHANGELOG.md) for changes and compatibility notes.
489
+ See [the changelog](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/CHANGELOG.md) for changes and compatibility notes.
428
490
 
429
491
  ## Research and prior art
430
492
 
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0] - 2026-09-15
4
+
5
+ ### Added
6
+
7
+ - Shared literal input for `programs`: top-level `data` defaults each entry, while
8
+ explicit entry data replaces it entirely, including falsy values. Every fresh
9
+ guest receives its own copy. The combined JSON admission budget counts common
10
+ input once; deadlines, host-call limits, separate commits and stop reports stay
11
+ unchanged. No implicit object merge, shared heap or inferred plan is introduced.
12
+
13
+ ### Hardened execution and reads
14
+
15
+ - Keep byte-accurate conflict snapshots independent of receipt/body caches.
16
+ Explicit rereads refresh observations; internal diff reads and cache eviction do
17
+ not rebase pending writes. Partial and focused reads retain full-file signatures,
18
+ including above 16 MiB, and invalid UTF-8 no longer causes a false conflict.
19
+ - Canonicalize new-file destinations before checking conflicting symlink aliases,
20
+ while preserving the existing logical paths in workspace-change notifications.
21
+ - Preserve read/mutation/checkpoint ordering across coalesced read waves. Tighten
22
+ input validation, cancellation handling and bounded output without reusing an
23
+ executed worker or reducing individual independent-read budgets.
24
+ - Keep captured read overrides authoritative when options are supplied. Align
25
+ native/guest evidence results and path-array aliases; reject incompatible modes
26
+ and enforce the same focused-query keyword cap for disk and staged content.
27
+ - Discover newly staged declarations in file-scoped queries and large overlays.
28
+ Preserve line endings, EOF characters and post-edit line coordinates. Distinguish
29
+ absent matches from matches that exceed the view budget. `complete` consistently
30
+ means the whole file; extensionless paths support `complete:true`.
31
+
32
+ ### Documentation and verification
33
+
34
+ - Document shared-input examples, commit/rollback and override boundaries, and the
35
+ need for a full host restart after JavaScript updates. `/reload` can retain old
36
+ native ESM modules; pushing GitHub does not update an npm installation.
37
+ - Restore the historical token fixture and hash-lock it. Version the single
38
+ terminating-newline expectation separately, without changing historical traffic,
39
+ programs, decision boundaries or acceptance thresholds.
40
+ - Add failure-first regressions for reviewed and newly found defects. Correct
41
+ oversized fixtures, non-finite timeout inputs and misleading test descriptions.
42
+ - Measure shared-input audits with identical complete outputs: 16,309 to 5,499
43
+ tokens (o200k_base) and 14,649 to 5,197 (cl100k_base), including replay, result
44
+ framing and added standing guidance. These are workload-specific non-compressive
45
+ savings, not provider-billing or live-model quality claims. See
46
+ [token measurements](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/docs/TOKEN_COSTS.md)
47
+ for the unchanged historical gates, accounting and reproduction commands.
48
+
49
+ ## [0.5.0] - 2026-09-12
50
+
51
+ - Report overlapping `supernova` calls from every participant in the wave, including the first-started call that finishes last. Start-order or finish-order counters missed that side; a peak concurrent count resets when the wave drains. Sequential calls, `programs` batches, and failed programs still do not leak a split hint. The regression forces the slower first program so the last-finisher case cannot flake under load.
52
+
53
+ - Shrink the standing tool definition without compressing source or results. Duplicate object-form restatements, schema prose already covered by the command list, and discoverable operational asides are gone; signatures and safety rules stay in the always-sent reference. Frozen six-call traffic is 14,970 / 14,787 tokens (o200k / cl100k): 19.23% / 19.24% below d444eb7. The current-pass gate now requires 19% vs d444eb7.
54
+
55
+ - An unmatched or non-unique edit keeps the file and returns a numbered window of the actual source (16-line cap, same coordinates as a successful edit) so the next program can copy oldText without a blind re-read. Successful read/write/edit results are unchanged.
56
+
57
+ - A program that writes or edits and returns nothing still delivers those mutation receipts (numbered post-edit lines, `wrote` paths). Reads without a return stay a no-return hint and do not dump file contents. Explicit `return await edit(...)` is not duplicated.
58
+
59
+ - `edit(view, text)` replaces a `resolve:true` window by line span with CAS against the viewed bytes. Duplicate substrings no longer block a ranged edit; a stale view fails and rolls back. The silent receipt names that span (`edited path:2-2`), not a ±2 context window.
60
+
61
+ - A `resolve:true` snap of a declaration is that declaration's span, not the whole file just because the file fits the read budget. `edit(view, text)` then replaces the function, not the file. `edit(view, old, new)` is unique inside that span. A budget-clipped view (`nextOffset`) is not editable.
62
+
63
+ - Gravity: `read("symbol")` is the same view as `read({query, resolve:true})`. `read(path)` stays raw text. `looksLikePath` lives in `shared/decode.js` so guest and host share the identifier-vs-path rule.
64
+
65
+ - Nested declaration spans: a parent still includes its body (brace-matched from the opening line), one-line class methods resolve, and two exact same-name declarations in one file are ambiguous instead of snapping the first. Ambiguous candidates (same-file or cross-file) are that hit's span (signature, lines, text, context), not a clone of the first match's window. Span pick/slice live in `src/context/spans.js` for locate + host resolve.
66
+
67
+ - Do not subscribe a `context` observer when `seenWindow` is 0. A no-op listener still ran on every provider context event; opt-in retention windows still register.
68
+
69
+ - Encode the module BIND spine as a regression: acyclic imports, no upward edges, context and runtime stay siblings. host-bridge remains the fused INVOKE kernel.
70
+
71
+ - Drop guest/host RPC for `search` and `describe`, and stop returning unused `exec`/`patch`/`nova` bindings from the worker API. The guest still injects only read, write, edit, bash.
72
+
3
73
  ## [0.4.0] - 2026-09-10
4
74
 
5
75
  - Disable citation elision by default (`seenWindow: 0`), including direct bridge/ledger defaults. A failure-first integration regression preserves complete results across hidden, reordered and removed context. Positive windows remain an explicit research opt-in, not a validated retention guarantee. The historical ledger measurements below describe that experimental mode only.
@@ -1,11 +1,12 @@
1
1
  # Token usage and benchmarks
2
2
 
3
- Supernova reduces repeated tool traffic through explicit program reuse and batching.
3
+ Supernova reduces repeated tool traffic through explicit program reuse, batching
4
+ and shared batch input defaults.
4
5
  It can also reduce escaping in nested multiline results with lossless text framing.
5
6
  These mechanisms do not summarize results, rewrite conversation history, or change
6
7
  reasoning settings. Savings depend on the workload.
7
8
 
8
- See the [API guide](../README.md) for program-file and batch usage.
9
+ See the [API guide](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/README.md) for program-file and batch usage.
9
10
 
10
11
  ## Reproduce
11
12
 
@@ -69,30 +70,26 @@ text contributes to totals through later history, not as a second charge.
69
70
 
70
71
  ## Measured results
71
72
 
72
- Observed on macOS with Node v26.7.0 and Linux aarch64 with Node v24.16.0; both
73
- produced the same token counts.
73
+ Observed for 0.6.0 on 2026-09-15, on an Apple M5 Max running macOS and Node v26.7.0.
74
74
 
75
75
  | Tokenizer | Non-batched baseline | Batched baseline (d444eb7) | Current | Further reduction | Total reduction |
76
76
  | --- | ---: | ---: | ---: | ---: | ---: |
77
- | o200k_base | 40,129 | 18,535 | 17,581 | **5.15%** | **56.19%** |
78
- | cl100k_base | 39,697 | 18,310 | 17,363 | **5.17%** | **56.26%** |
77
+ | o200k_base | 28,130 | 18,535 | 9,843 | **46.90%** | **65.01%** |
78
+ | cl100k_base | 27,841 | 18,310 | 9,726 | **46.88%** | **65.07%** |
79
79
 
80
80
  The gate requires at least 40% reduction on **each tokenizer for the complete
81
81
  workload**, not for every scenario individually. Token counts and reductions are
82
82
  computed from the recorded baseline and fresh execution results, not constants
83
- returned by the runtime. A second gate requires another 5% against the measured
83
+ returned by the runtime. A second gate requires another 19% against the measured
84
84
  batched baseline from commit d444eb7. Its six-call argument hash is pinned as well
85
85
  as the original workload: removing a decision boundary cannot satisfy this gate.
86
86
 
87
- ### What changed after the batched baseline
87
+ ### Definition and result accounting
88
88
 
89
- The serialized definition falls from 1,068 to 947 tokens with o200k_base and from
90
- 1,057 to 937 with cl100k_base. Repeated guidance now has one model-visible home;
91
- the command reference, safety rules and schema constraints remain available.
92
- Batch framing declares UTF-16 length units once instead of on every entry and
93
- omits redundant aggregate mutation totals from the wrapper. Every original
94
- per-program result, including its mutation report, remains intact. Structured
95
- aggregate counters are unchanged. No source text or independent result is removed.
89
+ The current serialized definition is 631 tokens with o200k_base and 626 with
90
+ cl100k_base, versus 908 and 901 in the frozen non-batched baseline. It retains
91
+ command signatures, complete-read and JSON limits, array-read failure rules,
92
+ transaction boundaries, batch defaults and edit/view guidance on every request.
96
93
 
97
94
  For the fixed six-call schedule, the accounting can also be written as:
98
95
 
@@ -100,14 +97,9 @@ For the fixed six-call schedule, the accounting can also be written as:
100
97
  Total = (N+1)*D + sum((N-i+2)*A_i + (N-i+1)*R_i, i = 1..N)
101
98
  ~~~
102
99
 
103
- Seven definition appearances save 7*121 = 847 tokens with o200k_base. The smaller
104
- batch wrappers save another 107 after history replay, for 954/18,535 = 5.15%.
105
- For cl100k_base the corresponding saving is 7*120 + 107 = 947 tokens. The logical
106
- programs, their arguments, complete results and decision boundaries are unchanged.
107
-
108
- There are still costs: the current definition exceeds the non-batched baseline
109
- by 39/36 tokens per request, and batching adds result framing. One-off calls should
110
- not be assumed to benefit from the batch API.
100
+ The definition is counted seven times, including the final handoff. Batch result
101
+ framing and every attempted program's text are counted too. One-off calls should
102
+ not be assumed to benefit from batching.
111
103
 
112
104
  The report also includes separate source-framing and argument-reuse comparisons.
113
105
  Those component measurements are not total-session savings, and the reported
@@ -115,28 +107,70 @@ argument-only break-even excludes other request costs. Text framing is selected
115
107
  by character length, not a runtime tokenizer; it need not reduce tokens for every
116
108
  input or encoding.
117
109
 
110
+ ## Shared batch input: a separate 0.6.0 measurement
111
+
112
+ Eight independent audit programs use the same list of 48 source paths. The before
113
+ arm repeats the literal `data` in every entry; the after arm supplies it once at
114
+ the top level. Both execute the same programs and return the same complete source
115
+ strings and typed results. The before request fits the existing admission cap;
116
+ this is not a comparison against a hypothetical request that could never run.
117
+
118
+ | Tokenizer | Repeated-input traffic | Shared-input traffic | Reduction | Arguments before / after | Unchanged result tokens |
119
+ | --- | ---: | ---: | ---: | ---: | ---: |
120
+ | o200k_base | 16,309 | 5,499 | **66.28%** | 6,461 / 1,043 | 2,151 |
121
+ | cl100k_base | 14,649 | 5,197 | **64.52%** | 5,684 / 945 | 2,055 |
122
+
123
+ Each arm has one tool invocation followed by the final answer request:
124
+
125
+ ~~~text
126
+ Total = 2*D + 2*A + R
127
+ ~~~
128
+
129
+ Arguments are charged when generated and when replayed; the complete result is
130
+ charged on handoff. The new standing guidance adds 13 definition tokens per
131
+ request (618 to 631 / 613 to 626), and that cost is included in the after totals.
132
+ There is no source compression, result elision, hidden output or lost decision
133
+ boundary. Programs receive fresh data copies, not a shared mutable heap.
134
+
135
+ The gate requires at least 70% less argument traffic and 60% less replay-inclusive
136
+ traffic in each encoding, plus equality of the complete normalized output. This
137
+ workload deliberately exercises repeated input; it is not an average task-cost
138
+ estimate. The original 13-program/six-call benchmark remains separate and intact.
139
+
118
140
  ## Benchmark integrity
119
141
 
120
- The [baseline fixture](../tests/efficiency/token-baseline.json) contains the source
142
+ The [baseline fixture](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/tests/efficiency/token-baseline.json) contains the source
121
143
  inputs, definition, workload hash, arguments and complete outputs. The
122
- [runner](../tests/efficiency/workflow.mjs) executes the registered tool with real
144
+ [runner](https://github.com/AdityaVG13/pi-stack/blob/main/packages/pi-supernova/tests/efficiency/workflow.mjs) executes the registered tool with real
123
145
  workers and filesystem operations. It checks that:
124
146
 
125
147
  - The workload hash matches the frozen baseline, and the six-call argument hash
126
148
  matches the prior batched execution.
127
- - Every original argument, complete logical result and expected failure matches.
149
+ - Every original argument and expected failure matches; complete logical results
150
+ match the explicit current newline contract described below.
128
151
  - Batched output contains every original result, with no unaccounted outer text.
129
152
  - No result is truncated, and final repaired source and JSON contents match.
130
153
 
131
154
  Only run IDs, elapsed times and temporary workspace prefixes in write receipts
132
155
  are normalized. Batch framing lengths are adjusted to match that normalized text.
133
156
  The workload, batch schedule, recorded comparison totals and acceptance thresholds
134
- are fixed test inputs, not production execution rules. The baseline is not regenerated by the benchmark.
157
+ are fixed test inputs, not production execution rules. The baseline is not regenerated
158
+ by the benchmark and is checked against its SHA-256 before execution.
159
+
160
+ Contract v2 preserves the terminating newline of one selected source line. The
161
+ runner derives that single expected-output correction from the frozen input, not
162
+ from candidate output. The historical fixture and its traffic counts stay
163
+ untouched; programs, arguments, failures and decision boundaries are unchanged.
164
+ The shared-input comparison separately requires equal complete result text in
165
+ both arms, after only run-metadata normalization.
166
+
167
+ The README and these docs ship in the npm tarball. Benchmarks and test fixtures
168
+ remain in the GitHub checkout, so their links above use GitHub URLs.
135
169
 
136
170
  Recorded baseline SHA-256:
137
171
 
138
172
  ~~~text
139
- 79a819eca82c8a5ff381e96b7669d8a5bf04fabbc8c10cf02f3a7c8817171b62
173
+ 96964f990f481ac05afaefdd02001bd15f61835a8381349a61e38c06209d7508
140
174
  ~~~
141
175
 
142
176
  ## Limitations
@@ -148,6 +182,8 @@ Recorded baseline SHA-256:
148
182
  reasoning settings does not establish unchanged end-to-end task quality.
149
183
  - Batching is appropriate only for already-chosen continuations. Actions requiring
150
184
  a new model decision must remain separate calls.
185
+ - The report also has an experimental citation-elision arm. It is disabled by
186
+ default and is not the source of the non-compressive savings reported here.
151
187
  - Existing read, output, log, image and execution limits still apply. The
152
188
  benchmark does not obtain savings by lowering them or hiding truncation.
153
189
 
package/index.js CHANGED
@@ -4,7 +4,7 @@ import { REFERENCE } from "./src/runtime/reference.js";
4
4
  import { isString, isFunction } from "./src/shared/decode.js";
5
5
  import { loadConfig } from "./src/config/config.js";
6
6
  import { createHostBridge } from "./src/bridge/host-bridge.js";
7
- import { truncateChars } from "./src/output/format.js";
7
+ import { truncateChars, formatBoundedStringArray } from "./src/output/format.js";
8
8
  import { runGuestProgram, warmGuestWorker, stopWarmGuestWorker } from "./src/runtime/runtime.js";
9
9
  import { renderSupernovaCall, renderSupernovaResult } from "./src/ui/render.js";
10
10
 
@@ -67,7 +67,10 @@ export function progressEmitter(onUpdate) {
67
67
  const wait = PROGRESS_FRAME_MS - (performance.now() - lastSent);
68
68
 
69
69
  if (wait <= 0) send();
70
- else timer = setTimeout(send, wait);
70
+ else {
71
+ timer = setTimeout(send, wait);
72
+ timer.unref?.();
73
+ }
71
74
  };
72
75
 
73
76
  emit.flush = () => {
@@ -99,6 +102,15 @@ function mutationText(outcome) {
99
102
  return "\nmutations: committed=" + m.committed + " rolledBack=" + m.rolledBack + " (file versions)" + external + uncertain;
100
103
  }
101
104
 
105
+ function mutationReceipts(trace) {
106
+ if (!Array.isArray(trace)) return "";
107
+
108
+ return trace
109
+ .filter(row => row?.ok && (row.name === "write" || row.name === "edit") && isString(row.resultText) && row.resultText)
110
+ .map(row => row.resultText)
111
+ .join("\n");
112
+ }
113
+
102
114
  // Corrective hint, emitted only when a turn actually split. Independent work
103
115
  // belongs in one program: a split cannot use the single prewarmed worker and pays
104
116
  // one extra spawn per sibling. Costs nothing until it fires, so it needs no room in
@@ -119,6 +131,25 @@ function successText(outcome, call) {
119
131
  return `ok #${call} ${outcome.wallMs}ms${truncated}${outcome.mutations?.committed || outcome.mutations?.rolledBack || outcome.mutations?.external ? mutationText(outcome) : ""}${splitTurnHint(outcome)}${logsBlock(outcome, "\n--- result")}\n${outcome.resultText}${hint}`;
120
132
  }
121
133
 
134
+ function fitOutput(outcome, call, limit, format) {
135
+ let text = format(outcome, call);
136
+
137
+ if (text.length <= limit) return text;
138
+ outcome.returnTruncated = true;
139
+ const wrapper = format({ ...outcome, resultText: "", logs: [] }, call);
140
+ const room = Math.max(256, limit - wrapper.length);
141
+
142
+ if (Array.isArray(outcome.result) && outcome.result.length && outcome.result.every(isString)) {
143
+ outcome.resultText = formatBoundedStringArray(outcome.result, room);
144
+ } else if (isString(outcome.resultText) && outcome.resultText.length > room) {
145
+ outcome.resultText = truncateChars(outcome.resultText, room, "output").text;
146
+ }
147
+
148
+ text = format(outcome, call);
149
+
150
+ return text.length <= limit ? text : truncateChars(text, limit, "output").text;
151
+ }
152
+
122
153
  const TOOL_DESCRIPTION = REFERENCE;
123
154
 
124
155
  export default function piSupernova(pi) {
@@ -140,6 +171,9 @@ export function registerCodeMode(pi) {
140
171
  // Counting them lets a result say so without adding standing guidance to the
141
172
  // tool definition, which is resent on every request.
142
173
  let inFlight = 0;
174
+ // Peak concurrent execute() bodies in the current wave. Start-order or
175
+ // finish-order alone cannot see a first-started call that finishes last.
176
+ let overlapPeak = 0;
143
177
 
144
178
  function cancelWarmTimer() {
145
179
  if (warmTimer !== undefined) clearImmediate(warmTimer);
@@ -176,17 +210,17 @@ export function registerCodeMode(pi) {
176
210
  name: "supernova",
177
211
  label: "Supernova",
178
212
  description: TOOL_DESCRIPTION,
179
- promptSnippet: "JavaScript with read, write, edit, and bash",
213
+ promptSnippet: "read, write, edit, bash",
180
214
  parameters: Type.Object({
181
215
  code: Type.Optional(Type.String({ maxLength: config.maxCodeChars ?? 48000 })),
182
216
  file: Type.Optional(Type.String({ minLength: 1 })),
183
- data: Type.Optional(Type.Unknown({ description: "Literal JSON input available as data in the program; put Markdown, scripts or argv here instead of nesting JavaScript quoting. JSON-encoded size is limited to the code character budget." })),
184
- timeoutMs: Type.Optional(Type.Integer({ minimum: 1000, description: "Hard timeout in ms." })),
217
+ data: Type.Optional(Type.Unknown()),
218
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1000 })),
185
219
  programs: Type.Optional(Type.Array(Type.Object({
186
220
  code: Type.Optional(Type.String({ maxLength: config.maxCodeChars ?? 48000 })),
187
221
  file: Type.Optional(Type.String({ minLength: 1 })),
188
222
  data: Type.Optional(Type.Unknown()),
189
- }, {additionalProperties:false}), {minItems:1,maxItems:32,description:"Instead of top-level code/file/data. JSON-encoded array shares the code character cap."})),
223
+ }, {additionalProperties:false}), {minItems:1,maxItems:32})),
190
224
  }),
191
225
  // One self-owned result frame is shared by Pi and OMP; renderCall stays empty
192
226
  // so separate call/result slots cannot duplicate the lifecycle card.
@@ -197,7 +231,7 @@ export function registerCodeMode(pi) {
197
231
  execute: async function execute(_id, params, signal, onUpdate, ctx, budget) {
198
232
  if (params?.programs !== undefined) return runProgramBatch(_id,params,signal,onUpdate,ctx,config,execute);
199
233
  cancelWarmTimer();
200
- const runCwd = ctx?.cwd || cwd;
234
+ const runCwd = isString(ctx?.cwd) && ctx.cwd ? ctx.cwd : cwd;
201
235
  const runController = new AbortController();
202
236
  const abortRun = () => runController.abort(signal?.reason);
203
237
 
@@ -214,8 +248,9 @@ export function registerCodeMode(pi) {
214
248
  emitProgress([]);
215
249
  const started = performance.now();
216
250
  let outcome;
217
- const overlappedTurn = inFlight > 0 ? inFlight + 1 : 0;
218
251
  inFlight += 1;
252
+ overlapPeak = Math.max(overlapPeak, inFlight);
253
+ let peakSeen = overlapPeak;
219
254
 
220
255
  try {
221
256
  refreshCatalog(runBridge);
@@ -226,7 +261,7 @@ export function registerCodeMode(pi) {
226
261
  cwd: runCwd,
227
262
  data: params?.data,
228
263
  nova: makeNovaApi(runBridge, abortRun),
229
- config: { ...config, maxLogLines: Math.max(0,config.maxLogLines-(budget?.logLines ?? 0)), timeoutMs: Number.isInteger(params?.timeoutMs) ? params.timeoutMs : config.timeoutMs },
264
+ config: { ...config, maxLogLines: Math.max(0,config.maxLogLines-(budget?.logLines ?? 0)), timeoutMs: params?.timeoutMs === undefined ? config.timeoutMs : Number(params.timeoutMs) },
230
265
  signal: runController.signal,
231
266
  onTimeout: abortRun,
232
267
  });
@@ -244,7 +279,9 @@ export function registerCodeMode(pi) {
244
279
  while (runBridge.getOverlayDepth()) runBridge.rollbackSpeculation();
245
280
  outcome = { ok: false, error: error instanceof Error ? error.message : String(error), logs: outcome?.logs ?? [], wallMs: Math.round(performance.now() - started) };
246
281
  } finally {
282
+ peakSeen = Math.max(peakSeen, overlapPeak);
247
283
  inFlight -= 1;
284
+ if (inFlight === 0) overlapPeak = 0;
248
285
  runBridge.setCallListener(null);
249
286
  emitProgress.flush();
250
287
  signal?.removeEventListener("abort", abortRun);
@@ -264,21 +301,20 @@ export function registerCodeMode(pi) {
264
301
  }
265
302
 
266
303
  if (budget) budget.logLines += outcome.logs?.length ?? 0;
267
- // inFlight has dropped by now, so a non-zero value means a sibling is still
268
- // running: report the overlap from either side so the hint does not depend on
269
- // which invocation happened to start first.
270
- outcome.overlappedTurn = overlappedTurn || (inFlight > 0 ? inFlight + 1 : 0);
304
+ outcome.overlappedTurn = peakSeen > 1 ? peakSeen : 0;
271
305
  outcome.mutations = runBridge.getMutations();
272
306
  const trace = runBridge.getTrace();
273
- const format = outcome.ok ? successText : errorText;
274
- let text = format(outcome, call);
275
307
 
276
- if (text.length > config.maxReturnChars) {
277
- outcome.returnTruncated = true;
278
- text = format(outcome, call);
279
- }
308
+ if (outcome.ok && outcome.result === undefined) {
309
+ const receipts = mutationReceipts(trace);
280
310
 
281
- const bounded = truncateChars(text, config.maxReturnChars, "output").text;
311
+ if (receipts) {
312
+ outcome.resultText = receipts;
313
+ outcome.undefinedReturn = false;
314
+ }
315
+ }
316
+ const format = outcome.ok ? successText : errorText;
317
+ const bounded = fitOutput(outcome, call, config.maxReturnChars, format);
282
318
  const visible = runBridge.ledger.dedupe(bounded, call);
283
319
 
284
320
  const response = result(visible, {
@@ -300,10 +336,13 @@ export function registerCodeMode(pi) {
300
336
  });
301
337
 
302
338
  // This is a pre-conversion observation, not a final-payload retention proof.
303
- // With the shipping seenWindow:0 default, observe is a no-op.
304
- pi.on("context", event => {
305
- try { bridge.ledger.observe(event?.messages); } catch {}
306
- });
339
+ // Shipping seenWindow:0 must not subscribe: a no-op listener still runs on every
340
+ // provider context event. Opt-in windows register here.
341
+ if ((config.seenWindow ?? 0) > 0) {
342
+ pi.on("context", event => {
343
+ try { bridge.ledger.observe(event?.messages); } catch {}
344
+ });
345
+ }
307
346
 
308
347
  pi.on("session_shutdown", () => { stopped = true; cancelWarmTimer();
309
348
 
@@ -311,7 +350,7 @@ export function registerCodeMode(pi) {
311
350
  pi.on("session_start", (_event, ctx) => {
312
351
  stopped = false;
313
352
 
314
- if (ctx && isString(ctx.cwd) && ctx.cwd) cwd = ctx.cwd;
353
+ cwd = ctx && isString(ctx.cwd) && ctx.cwd ? ctx.cwd : process.cwd();
315
354
  // A new session is a new model context: nothing has been seen yet.
316
355
  bridge.bindCallContext(ctx);
317
356
  bridge.ledger.reset();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-supernova",
3
- "version": "0.4.0",
4
- "description": "One CodeMode invocation for Pi and OMP, with four guest commands, automatic read batching and source context.",
3
+ "version": "0.6.0",
4
+ "description": "CodeMode for Pi and OMP: read, edit, write and bash, with transactional files, source views and shared-input program batches.",
5
5
  "type": "module",
6
6
  "author": "AdityaVG13",
7
7
  "license": "MIT",