@groeponline/pi-wishcraft 1.16.0 → 1.23.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.
Files changed (45) hide show
  1. package/AGENTS.md +52 -22
  2. package/CHANGELOG.md +65 -0
  3. package/CONTRIBUTING.md +12 -1
  4. package/RELEASE.md +142 -49
  5. package/bash-mode/completion.ts +2 -2
  6. package/build-stamp.json +1 -1
  7. package/docs/agents/domain.md +60 -0
  8. package/docs/agents/issue-tracker.md +88 -0
  9. package/docs/configuration.md +6 -1
  10. package/docs/design/regression-testing.md +1 -1
  11. package/docs/testing.md +81 -0
  12. package/package.json +17 -10
  13. package/src/config/settings-registry.ts +2 -1
  14. package/src/config/structural-preset-table.ts +0 -1
  15. package/src/extension/commands/commands.ts +0 -1
  16. package/src/extension/core/peer-guard.ts +7 -2
  17. package/src/extension/core/segment-context.ts +139 -44
  18. package/src/extension/core/state.ts +1 -0
  19. package/src/extension/core/types.ts +5 -0
  20. package/src/extension/queue/queue-integration.ts +20 -2
  21. package/src/extension/session/session-lifecycle.ts +24 -3
  22. package/src/extension/skills/inline-invocation.ts +1 -1
  23. package/src/extension/ui/custom-editor.ts +1 -5
  24. package/src/extension/ui/deck/config-diagnostic-lines.ts +0 -6
  25. package/src/extension/ui/ports-panel.ts +0 -1
  26. package/src/extension/ui/token-overlays.ts +1 -1
  27. package/src/extension/welcome/welcome-integration.ts +65 -5
  28. package/src/i18n/nl.ts +3 -0
  29. package/src/segments/core.ts +0 -1
  30. package/src/segments/system.ts +12 -3
  31. package/src/studio/component.ts +0 -1
  32. package/src/theme/theme.ts +1 -1
  33. package/src/tools/graph.ts +0 -3
  34. package/src/usage/tps-ring.ts +172 -32
  35. package/src/welcome/banner.ts +14 -0
  36. package/src/welcome/overlay.ts +139 -7
  37. package/src/welcome/quick-launch.ts +44 -0
  38. package/src/welcome/types.ts +13 -0
  39. package/src/welcome/widgets/sessions-widget.ts +13 -9
  40. package/src/welcome/widgets/shortcuts-widget.ts +13 -5
  41. package/src/welcome/widgets/whats-new-widget.ts +16 -3
  42. package/src/working-vibes/frames.ts +81 -5
  43. package/src/working-vibes/index.ts +6 -1
  44. package/src/working-vibes/native-indicator.ts +71 -0
  45. package/src/working-vibes/storage.ts +5 -3
package/AGENTS.md CHANGED
@@ -44,7 +44,7 @@ Each domain operates through `RuntimeState` callbacks:
44
44
  Domain modules must not import back into the hub for callbacks. Shared types and constants live in `src/extension/core/`. Verify no circular imports:
45
45
 
46
46
  ```bash
47
- npm run circular
47
+ bun run circular
48
48
  # → madge --circular src index.ts bash-mode queue
49
49
  ```
50
50
 
@@ -71,29 +71,43 @@ npm run circular
71
71
 
72
72
  ## Development Commands
73
73
 
74
+ Bun is the package manager and script runner (`packageManager: bun@1.4.2`);
75
+ the test suite still runs on Node's own runner because two suites depend on
76
+ `node:test` timer/PTY semantics that `bun test` does not reproduce (see
77
+ `docs/testing.md`).
78
+
74
79
  ```bash
75
- # Install dependencies
76
- npm ci
80
+ # Install dependencies (frozen lockfile)
81
+ bun install --frozen-lockfile
77
82
 
78
83
  # Type check (strict, no emit)
79
- npm run typecheck
84
+ bun run typecheck
85
+
86
+ # Lint (oxlint; warnings are errors)
87
+ bun run lint
88
+
89
+ # Run all tests (node:test, coverage thresholds)
90
+ bun run test
80
91
 
81
- # Run all tests
82
- npm test
92
+ # Typecheck + lint + tests in one go
93
+ bun run check
83
94
 
84
95
  # Check for circular imports
85
- npm run circular
96
+ bun run circular
86
97
 
87
98
  # Full package contract verification (for publish preparation)
88
- npm run verify:package
99
+ bun run verify:package
89
100
 
90
101
  # Local preview server (Deck/Signal/motion surfaces)
91
- npm run preview
102
+ bun run preview
103
+
104
+ # Enable the pre-commit hook (typecheck + lint + staged tests)
105
+ bun run hooks:install
92
106
 
93
107
  # Dockerized parallel testing (typecheck + test + circular)
94
108
  scripts/docker-test.sh [-n N]
95
109
 
96
- # Cloud agent bootstrap (Node 24, pi CLI install)
110
+ # Cloud agent bootstrap (bun, Node 24, pi CLI install)
97
111
  scripts/cloud-agent-install.sh
98
112
  scripts/cloud-agent-start.sh
99
113
  ```
@@ -104,7 +118,7 @@ Releases are CI-driven via GitHub Actions:
104
118
 
105
119
  ```bash
106
120
  # Locally: bump version, rewrite CHANGELOG, tag, optional push
107
- node scripts/release.mjs # or: npm run release
121
+ bun scripts/release.mjs # or: bun run release
108
122
 
109
123
  # Publish to npm (CI handles auth)
110
124
  scripts/npm-publish.sh
@@ -114,7 +128,7 @@ scripts/github-release.sh
114
128
  ```
115
129
 
116
130
  CI workflows (`.github/workflows/`):
117
- - `test.yml` — Node 24, typecheck, test, circular, verify:package, npm audit
131
+ - `test.yml` — parallel lanes (whitespace, typecheck, lint, test, circular, package-contract, audit) with a `verify` aggregator that keeps the required check name
118
132
  - `release.yml` — reuses test.yml, prepares release-candidate branch, publishes on tag, attaches `npm pack` + `SHA256SUMS`
119
133
  - `promote-release-candidate.yml` — promotes verified SHA to main, tags, dispatches publish
120
134
 
@@ -246,14 +260,16 @@ function createRuntimeState(hooks: {
246
260
  | Requirement | Detail |
247
261
  |---|---|
248
262
  | **Node** | ≥22.19 required (pi CLI floor, pinned via `engines`); Node 24 via nvm for CI and pi CLI |
249
- | **Package manager** | npm (lockfile v3) |
263
+ | **Package manager** | Bun 1.4.2 (`bun.lock`, `bun install --frozen-lockfile`); the npm CLI is used only for registry operations (`npm pack`, `npm publish`, `npm dist-tag`) |
264
+ | **Lint** | oxlint (`bun run lint`, `--deny-warnings`); config in `.oxlintrc.json`, `no-control-regex` off because ANSI SGR matching is the product |
265
+ | **Pre-commit** | `.githooks/pre-commit` (typecheck + oxlint + staged test files), enabled with `bun run hooks:install` |
250
266
  | **TypeScript** | 5.9.3, strict mode, `NodeNext` module/resolution, `allowImportingTsExtensions`, no build step |
251
267
  | **Test runner** | Node built-in `node:test` with `--experimental-strip-types` type stripping |
252
- | **Circular import check** | `madge --circular src index.ts bash-mode queue` (via `npm run circular`) |
268
+ | **Circular import check** | `madge --circular src index.ts bash-mode queue` (via `bun run circular`) |
253
269
  | **Import style** | `.ts` extensions on all imports, `node:`-prefixed builtins |
254
270
  | **Pi CLI** | Installed at `^0.99.2` (`pi-coding-agent`, `pi-ai`, `pi-tui` move together — the agent depends on matching `pi-ai`/`pi-tui`, so bumping one alone splits the tree); wrapped to run under Node 24 via nvm |
255
271
  | **Peer floor** | `peerDependencies` are `"*"` for the three Pi packages because Pi's own docs require it ("declare the host-provided packages listed above in peerDependencies with a `*` range"), and `scripts/verify-pi-package-contract.mjs` enforces it. `"*"` therefore cannot express a floor, so the real one is only enforced by the code using the SDK: `normalizeContext` (added in `pi-ai` 0.86.0, used by `src/working-vibes/provider.ts`) is the binding symbol — probed across every SDK release, all 12 runtime symbols resolve from 0.86.0 up. A consumer on 0.84.3 installs without warning, but the activation-time peer guard (`src/extension/core/peer-guard.ts`) names every missing symbol with its feature and floor, notifies at session start, and turns the `/vibe` failure into an actionable error; `tests/peer-guard.test.ts` keeps the guarded catalog in sync with the published import surface in both directions. |
256
- | **Dependency audit** | `npm run audit` (`scripts/audit-gate.mjs`) is the gate. `audit-baseline.json` lists accepted advisories, each pinned to its GHSA id **and** the install path(s) it covers, with a required written reason; new high/critical advisories fail even on an already-accepted package, a second vulnerable copy of an accepted package fails, a baselined advisory that stops reporting as high/critical also fails, an `npm audit` that errors out fails the gate instead of reading as "zero findings", and the baseline is excluded from the published tarball. |
272
+ | **Dependency audit** | `bun run audit` (`scripts/audit-gate.mjs`) is the gate. `audit-baseline.json` lists accepted advisories, each pinned to its GHSA id **and** the install path(s) it covers, with a required written reason; new high/critical advisories fail even on an already-accepted package, a second vulnerable copy of an accepted package fails, a baselined advisory that stops reporting as high/critical also fails, an `npm audit` that errors out fails the gate instead of reading as "zero findings", and the baseline is excluded from the published tarball. |
257
273
  | **Docker** | `fuse-overlayfs` storage driver for nested VM testing |
258
274
  | **Compound Engineering** | `.compound-engineering/` overlay; portable skills via `~/.agents/skills/ce-*`; native Cursor plugin disabled |
259
275
 
@@ -264,7 +280,7 @@ function createRuntimeState(hooks: {
264
280
  - **Runner**: Node's built-in `node:test` (flat per-file suites)
265
281
  - **Assertions**: `node:assert/strict`
266
282
  - **Type stripping**: `--experimental-strip-types` (no `tsc` compilation in tests)
267
- - **Invocation**: `npm test` runs `node --experimental-strip-types --test tests/**/*.test.ts`
283
+ - **Invocation**: `bun run test` runs `node --experimental-strip-types --test tests/**/*.test.ts` (bun owns install and script invocation; `bun test` itself is not used — see `docs/testing.md`)
268
284
 
269
285
  ### Test Organization
270
286
 
@@ -298,19 +314,23 @@ import { powerlineFooter } from "./index.ts";
298
314
  The `.github/workflows/test.yml` workflow enforces the full verification chain:
299
315
 
300
316
  ```bash
301
- npm run typecheck # tsc --noEmit
302
- npm test # node --test
303
- npm run circular # madge --circular
304
- npm run verify:package # verify-package.mjs + verify-pi-package-contract.mjs
305
- npm audit # security audit
317
+ bun run typecheck # tsc --noEmit
318
+ bun run lint # oxlint --deny-warnings
319
+ bun run test # node --test with coverage thresholds
320
+ bun run circular # madge --circular
321
+ bun run verify:package # verify-package.mjs + verify-pi-package-contract.mjs
322
+ bun run audit # security audit gate
306
323
  ```
307
324
 
325
+ Each gate is its own parallel job; the `verify` job aggregates them and stays
326
+ the single required status check.
327
+
308
328
  ### Release Verification
309
329
 
310
330
  Before any publish, `prepublishOnly` runs the full chain:
311
331
 
312
332
  ```bash
313
- tsc --noEmit && verify-package.mjs && verify-pi-package-contract.mjs
333
+ tsc --noEmit && oxlint --deny-warnings ... && verify-package.mjs && verify-pi-package-contract.mjs
314
334
  ```
315
335
 
316
336
  ### Dockerized Parallel Testing
@@ -322,3 +342,13 @@ scripts/docker-test.sh -n 4 # 4 parallel containers
322
342
  ```
323
343
 
324
344
  Each container runs `typecheck + test + circular` with a shared read-only `node_modules` bind mount.
345
+
346
+ ## Agent skills
347
+
348
+ ### Issue tracker
349
+
350
+ Dual-tracker: Linear for planning/features, GitHub for bugs/triage. See `docs/agents/issue-tracker.md`.
351
+
352
+ ### Domain docs
353
+
354
+ Single-context. Current domain source: AGENTS.md. CONTEXT.md and docs/adr/ to be added via `/domain-modeling` when needed. See `docs/agents/domain.md`.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,71 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.23.0] - 2026-11-08
6
+
7
+ ### Fixed
8
+ - **Releases reach npm again, with the token npm actually accepts.** Five tags — v1.18.0 through v1.22.0 — were cut but never published, so `npm install @groeponline/pi-wishcraft` still handed out 1.17.0 while the repository was at 1.22.0; the publish job of the v1.22.0 tag died with `ENEEDAUTH`. The pipeline assumed npm trusted publishing, and a probe run from this repository shows why that assumption was wrong: `id-token: write` is granted and the id token is present, but npm answers the OIDC token exchange for `@groeponline/pi-wishcraft` with **401**, so npmjs.com has no matching trusted publisher and npm fell back to "no credential at all". The `publish` job now carries the organisation `NPM_TOKEN` into `setup-node`'s `.npmrc` as `NODE_AUTH_TOKEN` (the probe confirms it authenticates as `chefgroeponline`), keeps `id-token: write` for the day the publisher is configured, and `RELEASE.md` records the measured state with the one-time npmjs.com steps to switch over. The version holes stay: npm versions need not be contiguous, so this is the first version on the registry since 1.17.0.
9
+
10
+ ### Changed
11
+ - **CI can start again: every third-party action is pinned to a commit SHA.** The org policy *all actions must be pinned to a full-length commit SHA* rejects `actions/checkout@v7` at job setup, so typecheck, lint, test, circular, package-contract, whitespace and the `verify` aggregator all failed in about three seconds with `steps: 0` — which reads like broken code, not a policy. `checkout`, `setup-node`, `setup-bun` and `cache` are pinned in `test.yml`, `preview.yml`, `deprecate-old-name.yml` and the `bun-setup` composite action, and `tests/workflow-guards.test.ts` gained a third guard that fails on any third-party `uses:` without a 40-hex ref, so the next `@v7` is caught locally. The throwaway `npm-auth-probe.yml` is gone: its question is answered above. (#139)
12
+
13
+ ## [1.22.0] - 2026-11-05
14
+
15
+ ### Changed
16
+ - **The workflow ordering rules are tested, not remembered.** Two release failures had the same shape — a job used a tool that was not set up yet, or that could not know its own context: `publish` called `bun` before `bun-setup` ran (`bun: command not found`), and `verify` called `gh workflow run` with no checkout to infer the repository from (`failed to run git: fatal: not a git repository`). `tests/workflow-guards.test.ts` now checks every workflow: a job that runs `bun` must install it first, and a job that calls `gh` without a checkout must pin `GH_REPO`. Both assertions were verified to fail against the pre-fix files. `RELEASE.md` also records that a stranded tag can only be replayed with the workflow file it was tagged with.
17
+
18
+ ## [1.21.0] - 2026-11-04
19
+
20
+ ### Fixed
21
+ - **The publish job died with `bun: command not found` before it reached npm.** The bun migration (#128) rewrote `node scripts/verify-release-tag.mjs` to `bun scripts/...` in the `publish` job but left the `bun-setup` step below it, so v1.20.0's publish failed in its second step and never touched the registry. `bun-setup` now runs right after `setup-node` — which also satisfies the `prepublishOnly` gate `npm publish` triggers — and a test pins the order.
22
+
23
+ ## [1.20.0] - 2026-11-04
24
+
25
+ ### Fixed
26
+ - **A verified release candidate can be promoted again.** The `verify` aggregator in `test.yml` has no checkout, so `gh workflow run promote-release-candidate.yml` could not resolve the repository and every dispatch died with `failed to run git: fatal: not a git repository` — since the parallel-CI rewrite (#128) no candidate was promoted at all, which is why `main` sat at 1.19.0 with two tags and no release. The step now pins `GH_REPO: ${{ github.repository }}`, and a test holds it there.
27
+ - **Releases publish through npm trusted publishing, so no credential can expire.** v1.18.0 and v1.19.0 were tagged but never reached npm: the org `NPM_TOKEN` was a revoked 40-character classic token, and npm answers a PUT for a scoped package with 404 (not 403) when the caller may not publish, so the failure read like "package not found". `release.yml`'s `publish` job now grants `id-token: write` and lets the npm CLI exchange that GitHub OIDC identity for a short-lived publish token; `NPM_TOKEN` is no longer read, `scripts/npm-token-check.sh` and the `npm whoami` preflight are gone, and the manual `diagnose-npm.yml` workflow is deleted. `npm-publish.sh` drops the marker-tag + `npm dist-tag add latest` dance — it only existed to republish a version *below* `latest`, which OIDC does not cover; plain `npm publish` sets `latest` itself. Provenance attestations now come for free. `RELEASE.md` documents the one-time trusted-publisher configuration and replaces the token-rotation runbook.
28
+ - **`RELEASE.md` describes the chain that actually runs.** The old text still promised `release.mjs auto --push` bumping and publishing in one job, a `package-lock.json` in the candidate and `npm ci` CI steps; it now documents verify → prepare candidate → Verify on the candidate ref → Promote → publish on the tag ref, with the two-file candidate (`package.json`, `CHANGELOG.md`).
29
+
30
+ ### Changed
31
+ - **Bun replaces npm for the development loop.** `bun.lock` (with `packageManager: bun@1.4.2`) replaces `package-lock.json`, `bun install --frozen-lockfile` replaces `npm ci` in every workflow and bootstrap script, and the package scripts run through `bun run` (`scripts/*.mjs` execute directly, no `npx`). The npm CLI stays where the npm *registry* is the interface: `npm pack`, `npm publish`, `npm dist-tag`. The test runner stays `node:test`: measured on 1.4.2 against the 1037-test suite of that moment, `bun test` reported 1035 pass / 2 fail (fake-timer and PTY-timing divergence in two suites) while `node --test` reported 1037 pass / 0 fail, so `bun run test` invokes Node's runner and `docs/testing.md` records the measurement and the condition for revisiting it.
32
+ - **oxlint is the lint gate.** The repository had no ESLint configuration, so this is a gate added rather than a linter swapped: `.oxlintrc.json` (correctness = error), `bun run lint` runs with `--deny-warnings`, CI fails on a single warning, and `bun run lint:fix` applies the automatic fixes. `no-control-regex` is off repo-wide because matching ANSI SGR sequences is the product. Getting to zero warnings removed 23 real findings: 21 unused imports/locals/parameters, one dead `readSkillBody()` call whose result nothing read, and one unsafe optional chain in a test assertion.
33
+ - **CI runs in parallel.** `test.yml` is now seven independent lanes — `whitespace`, `typecheck`, `lint`, `test`, `circular`, `package-contract`, `audit` — each installing from the frozen lockfile via the `.github/actions/bun-setup` composite action, with a `verify` aggregator job that keeps the required check name and the release-candidate promotion dispatch. Pull-request runs cancel superseded pushes; release-driven candidate runs never do.
34
+ - **Pre-commit hook.** `.githooks/pre-commit` (enable with `bun run hooks:install`) runs typecheck, oxlint and the test files the commit touches. The full suite stays CI's job.
35
+ - **The dependency audit gate is bun-native.** `npm audit` needs `package-lock.json`, which no longer exists, so `scripts/audit-gate.mjs` reads `bun audit --json` plus `bun pm ls --all`. The guarantees are restated in bun's terms and pinned by tests: new high/critical advisories fail by GHSA id and package name, a baselined advisory whose vulnerable range moved fails, an installed version the baseline does not list fails (the "second vulnerable copy" case, now version-based instead of path-based), a baselined advisory that stops reporting as high/critical fails, and an audit that cannot run exits 2.
36
+ - **Release candidates carry two files.** `bun.lock` stores no root version, so a bump changes exactly `package.json` and `CHANGELOG.md`; `release.mjs`, `promote-release-candidate.yml` and their tests no longer expect a lockfile rewrite. `bun install --frozen-lockfile` in the publish job proves the lock still matches the manifest.
37
+ - `audit-baseline.json` and `.oxlintrc.json` are excluded from the published tarball; `bun.lock` never ships because `files` does not match it.
38
+
39
+ ## [1.19.0] - 2026-11-04
40
+
41
+ ### Fixed
42
+ - **`/tps` reported near-zero averages and thousand-TPS peaks.** `pairwiseRates()` and `ratesFromRing()` treated `delta >= 0` as a rate, so every idle repaint pushed a `0` and dragged `avg` down; two samples a few ms apart straddling one token burst reported that burst as a spike. Idle deltas are now skipped (`> 0`), buckets under `TPS_MIN_PAIRWISE_DT_MS` (80 ms) are folded into the next one, and duplicate counts inside 40 ms are dropped at the door.
43
+ - **`/tps` was empty unless the `tps` segment was in the active preset.** The ring was only filled while that segment rendered. `message_update` now samples it (`sampleTpsFromSession`, throttled to 30 ms); the segment keeps its passive sample as a fallback.
44
+ - **Pi's own working spinner ignored `wishcraft.workingIndicatorStyle` and the motion policy.** The style was only used by status renderers. `before_agent_start` now hands Pi `ctx.ui.setWorkingIndicator({ frames, intervalMs })` once per run (Pi owns the timer, so there is no second clock), and `agent_end` restores the default. Reduced/off motion holds one frame, functional alternates two, ASCII glyph mode forces the ASCII set.
45
+ - **Welcome overlay**: the boot reveal repainted once a second because only the countdown timer drove it; it now repaints on the same bounded heartbeat as the header, and keeps repainting while the lantern animates instead of freezing on the last frame once the reveal window closes (`revealElapsedForArt()`). The countdown timer is `unref`'d.
46
+ - **`/tps` averaged cached prefill into the input rate and smoothed nothing.** Each turn is now measured from `agent_start` to its first new token (TTFT), the input rate counts only uncached tokens (`input - cacheRead`), and the output rate carries an EMA (α 0.32) next to the raw window rate. The `ttft` and `ema` overlay lines appear only when a turn could actually be measured; a turn whose prompt was fully cached reports `0`, not a division artefact.
47
+ - Idea capture strips spinner noise for **every** indicator frame, not just braille, so the new native styles cannot leak into captured text. The prefix is now derived from the built-in frames *and* every Motion Catalog frame (whitespace-only rows included) at import time, so a new style cannot silently bypass it.
48
+ - `segment-context.ts` lost its last `any`: `buildSegmentContext()` and `sampleTpsFromSession()` take a structural `SegmentSourceContext` instead of `ctx: any`, and the builder is split into `resolveContextMetrics()` / `resolveCustomItemState()` so its complexity drops below the CodeFactor threshold.
49
+
50
+ ### Added
51
+ - Working-indicator styles `lantern` and `braille` (settings registry choices are now derived from `workingIndicatorStyles()` so the two cannot drift).
52
+ - Working-indicator style `motion`: Pi's native spinner is fed from the Motion Catalog (`framesOf()` plus each definition's `generator.intervalMs`) instead of a fixed glyph set, so the working indicator moves like the motion assigned to `thinking`. It falls back to `lantern` for an unknown motion id, and reduced/off motion, functional motion and ASCII glyph mode still win over the catalog.
53
+ - Welcome cockpit: `Tab`/`Shift+Tab` step between `What's new`, `Recent Crafts` and `Quick Launch`, ↑/↓ move inside the focused section and pause the countdown, and `Enter` (only once navigation has paused it) primes the prompt for the focused quick-launch row (`# `, `! `). Sessions stay read-only because Pi exposes no resume handle. Printable keys are still never captured: any letter dismisses, exactly as before.
54
+
55
+ ## [1.18.0] - 2026-11-03
56
+
57
+ ### Security
58
+ - Pi peers bumped to 1.5.0 (`@earendil-works/pi-coding-agent`, `pi-ai`, `pi-tui` move together): undici is now 8.12.3, clearing GHSA-xjj6-f33q-929c, and the peer dropped its `npm-shrinkwrap.json`, so the two inherited brace-expansion advisories (GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p) resolve to the fixed 5.0.12 as well. `audit-baseline.json` is back to zero accepted entries.
59
+
60
+ ### Fixed
61
+ - `before_agent_start` is a union in pi-coding-agent 1.5.0: the working-vibes prompt text now comes from `event.prompt` on user runs and from displayable custom-message content otherwise.
62
+
63
+ ## [1.17.0] - 2026-10-27
64
+
65
+ ### Fixed
66
+ - Bash paste-while-running (issue #72): a bracketed paste performed while a command runs is stripped of its markers and forwarded to the child stdin — including split delivery across input chunks — instead of silently queuing in the editor buffer behind the command.
67
+ - PTY/transcript memory bounds (from jan `fix/runtime-pty-bounds`): a single unterminated output line is capped at a 64 KiB UTF-8 tail with a one-time notice, the partial-escape tail is bounded, and the active transcript command trims head lines/bytes within limits instead of growing unbounded.
68
+ - Segment-options spread hygiene (CodeFactor #86–89): `normalizeSegmentOptions` assigns fields imperatively instead of spreading conditional empty objects.
69
+
5
70
  ## [1.16.0] - 2026-10-03
6
71
 
7
72
  ### Known limitations
package/CONTRIBUTING.md CHANGED
@@ -11,10 +11,21 @@ For PRs, keep the change narrow and include tests for behavior changes when poss
11
11
  Before opening a PR, run:
12
12
 
13
13
  ```bash
14
- npm test
14
+ bun run check # typecheck + oxlint + full test suite
15
15
  git diff --check
16
16
  ```
17
17
 
18
+ Or enable the fast pre-commit hook once per clone — it runs typecheck, oxlint
19
+ and only the test files you touched:
20
+
21
+ ```bash
22
+ bun run hooks:install
23
+ ```
24
+
25
+ Bun is the package manager (`bun install --frozen-lockfile`); the npm CLI is
26
+ reserved for registry operations (`npm pack`/`npm publish`). The test runner
27
+ stays Node's `node:test` — see `docs/testing.md` for why.
28
+
18
29
  ## Branching
19
30
 
20
31
  Trunk-based, with named prefixes. `main` is always releasable and protected; release tags are cut from it by `scripts/release.mjs`.
package/RELEASE.md CHANGED
@@ -12,37 +12,48 @@ verify it.
12
12
  - **GitHub repo:** `GroepOnline/pi-wishcraft` (forked from `nicobailon/pi-powerline-footer`, independently maintained)
13
13
  - **npm package:** `@groeponline/pi-wishcraft` (scoped, `--access public`)
14
14
  - **Default branch:** `main`
15
- - **Local checkout:** `~/Documents/Github/GroepOnline/pi-wishcraft`
15
+ - **Local checkout:** `/home/joep/Code/GroepOnline/pi-wishcraft/repo`
16
16
  - **Pi settings (`~/.pi/agent/settings.json`):** packages entry `npm:@groeponline/pi-wishcraft` and `"powerline": "chef"`. For active development, temporarily swap the entry to the local checkout path so edits are picked up on `/reload` — no reinstall needed; switch back to the npm name for the installed release.
17
17
 
18
18
  ## The release flow
19
19
 
20
- **Default:** every merge to `main` is a release. `.github/workflows/release.yml`
21
- runs `node scripts/release.mjs auto --push` on `main` and publishes in that
22
- same job (`scripts/npm-publish.sh`) with the GroepOnline org secret
23
- `NPM_TOKEN`, then creates a GitHub Release for the tag
24
- (`scripts/github-release.sh`) so the Releases page Latest matches npm. A tag
25
- pushed with `GITHUB_TOKEN` does **not** start another workflow, so publish
26
- cannot wait for the tag event. Manual SSH/PAT tag pushes still run the tag
27
- job. `auto` reads commit subjects since the last `v*` tag: `feat:` → minor,
20
+ **Default:** every merge to `main` is a release, but no version is cut on the
21
+ merge commit itself — `main` stays protected and the bump lands through a
22
+ verified candidate.
23
+
24
+ 1. `verify` runs the same lanes as a PR (`.github/workflows/test.yml`).
25
+ 2. `prepare` confirms verified `main` is still the head of `main` (otherwise a
26
+ newer push owns the next candidate) and cuts an idempotent candidate branch
27
+ `release-candidate/v<next>-<base-sha12>` holding only the
28
+ `chore: release <next>` bump of `package.json` and `CHANGELOG.md`
29
+ (`bun.lock` stores no root version, so it never changes).
30
+ 3. `prepare` dispatches Verify on that exact candidate ref.
31
+ 4. The final step of that Verify run dispatches
32
+ `.github/workflows/promote-release-candidate.yml`, which re-validates the
33
+ candidate from GitHub metadata only (branch name, parent == `main`, commit
34
+ subject, exactly those two files, both versions, and the
35
+ `## [<version>] - <date>` heading), fast-forwards `main` to the candidate
36
+ SHA, and creates tag `v<version>` via the API.
37
+ 5. A tag created with `GITHUB_TOKEN` emits no `push` event, so Promote
38
+ explicitly dispatches `release.yml` on the tag ref. That run's `publish` job
39
+ is what publishes to npm and creates the GitHub Release.
40
+
41
+ `auto` reads commit subjects since the last `v*` tag: `feat:` → minor,
28
42
  `feat!:` / `BREAKING CHANGE` → major, otherwise patch.
29
43
 
30
- The first merge after `v0.18.0` therefore becomes **0.19.0** (the 0.19
31
- `feat:` commits are already on `main`). Later docs/fix merges become
32
- `0.19.1`, `0.19.2`, …
33
-
34
- Put `[skip release]` in the merge-commit subject to opt out. The follow-up
35
- `chore: release X.Y.Z` commit does not tag again.
44
+ Put `[skip release]` in the merge-commit subject to opt out; the follow-up
45
+ `chore: release X.Y.Z` commit is skipped the same way. When `CHANGELOG.md` has
46
+ no `## [Unreleased]` notes, `prepare` reports "nothing to release" and cuts no
47
+ candidate.
36
48
 
37
49
  Manual still works when you want an explicit version before pushing tags:
38
50
 
39
51
  ```bash
40
- # 1. Make sure the working tree is clean and tests/typecheck pass (see Verification)
41
- npm run typecheck # tsc --noEmit
42
- npm test # node --experimental-strip-types --test tests/**/*.test.ts
52
+ # 1. Make sure the working tree is clean and the full chain passes (see Verification)
53
+ bun run check # typecheck + oxlint + tests
43
54
 
44
55
  # 2. Release (patch | minor | major | auto | explicit 1.2.3)
45
- npm run release minor # = node scripts/release.mjs minor
56
+ bun run release minor # = bun scripts/release.mjs minor
46
57
  ```
47
58
 
48
59
  `scripts/release.mjs` (zero deps, node built-ins only) does:
@@ -53,8 +64,9 @@ npm run release minor # = node scripts/release.mjs minor
53
64
  4. `git commit -m "chore: release <version>"`
54
65
  5. `git tag -a v<version> -m "Release <version>"`
55
66
 
56
- Locally it does **not** push unless you pass `--push` (CI does). Manual push
57
- uses the GroepOnline SSH identity (the laptop default SSH key is denied):
67
+ Locally it does **not** push unless you pass `--push` (CI does not use `--push`
68
+ anymore: `prepare` pushes only the candidate branch). Manual push uses the
69
+ GroepOnline SSH identity (the laptop default SSH key is denied):
58
70
 
59
71
  ```bash
60
72
  GIT_SSH_COMMAND='ssh -F ~/.ssh/config-groeponline -o IdentityFile=~/.ssh/sheesh' \
@@ -64,28 +76,107 @@ GIT_SSH_COMMAND='ssh -F ~/.ssh/config-groeponline -o IdentityFile=~/.ssh/sheesh'
64
76
  - SSH host alias: `github.com-groeponline` (defined in `~/.ssh/config-groeponline` with `IdentityFile ~/.ssh/sheesh`, `IdentitiesOnly yes`).
65
77
  - Push actor: `chefadmin-netizen` (GroepOnline SSH). API/`gh` actor: `MisterWanted` (FG) via `chef-gh`.
66
78
 
67
- ## CI publish
68
-
69
- `.github/workflows/release.yml` publishes from the **bump job** after tagging,
70
- and also from a tag push (manual SSH/PAT). `GITHUB_TOKEN` tag pushes do not
71
- start a second run.
72
-
73
- 1. `actions/checkout@v4` (`fetch-depth: 0`, `fetch-tags: true`)
74
- 2. `actions/setup-node@v4` (node 24, `registry-url: https://registry.npmjs.org`)
75
- 3. Fetch and `git reset --hard origin/main` (bump job only; tests the tree that will be tagged)
76
- 4. `npm ci --ignore-scripts` (installs devDependencies incl. `typescript`)
77
- 5. `npm run typecheck` (tsc)
78
- 6. `npm test` (node test runner)
79
- 7. `npm run verify:package` (catalog contract gate — see `scripts/verify-package.mjs`)
80
- 8. `node scripts/release.mjs auto --push` (main only)
81
- 9. `sh scripts/npm-publish.sh` with `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` (fail-closed `npm view`, idempotent skip, then the Pi catalog URLs)
82
- 10. `sh scripts/github-release.sh` with `GITHUB_TOKEN` (idempotent: skip if that tag already has a Release; notes from `CHANGELOG.md`)
83
-
84
- `NPM_TOKEN` is the GroepOnline **org** Actions secret (publish rights to the
85
- `@groeponline` scope). This repo has no override; Actions inherits the org
86
- value. It is **not** readable after being set; verify it works by checking
87
- that the publish step on a real release succeeds and `npm view` resolves the
88
- new version (see below). The publish step fails closed if the secret is empty.
79
+ ## CI publish (tag ref only)
80
+
81
+ `release.yml`'s `publish` job runs only when `github.ref` is a `v*` tag: the
82
+ dispatch Promote fires, or a manual SSH/PAT tag push. It never runs on `main`.
83
+
84
+ 1. `actions/checkout` at the tag (`fetch-depth: 0`)
85
+ 2. `actions/setup-node` (node 24)
86
+ 3. Stamp `PI_WISHCRAFT_BUILD_SHA` and `build-stamp.json` from the tag commit only
87
+ 4. `bun scripts/verify-release-tag.mjs --tag <tag>` (tag == `package.json`, on `origin/main`)
88
+ 5. `bun install --frozen-lockfile --ignore-scripts` (devDependencies incl. `typescript`)
89
+ 6. `sh scripts/npm-publish.sh` (fail-closed `npm view`, idempotent skip, `npm publish --access public`)
90
+ 7. `sh scripts/github-release.sh` with `GITHUB_TOKEN` (idempotent: skip if that tag already has a Release; notes from `CHANGELOG.md`)
91
+ 8. `npm pack` + `SHA256SUMS`, uploaded as a run artifact and attached to the GitHub Release
92
+
93
+ The lanes that need to prove the tree (typecheck, lint, tests, package contract)
94
+ live in `test.yml` and run in `verify` — the tag commit already passed them as a
95
+ candidate, so `publish` does not repeat them.
96
+
97
+ ## npm authentication: the org `NPM_TOKEN`, with trusted publishing prepared
98
+
99
+ `release.yml` publishes with the organisation secret **`NPM_TOKEN`** (a classic
100
+ token for the `chefgroeponline` account). `setup-node` writes it into `~/.npmrc`
101
+ through `NODE_AUTH_TOKEN`, so the value lives only in the org secret store: not
102
+ in the repo, not in a dispatch input, never printed by a run.
103
+
104
+ Trusted publishing (OIDC) is wired but **not active**. The `publish` job still
105
+ grants `id-token: write`, but npm answers the OIDC exchange for
106
+ `@groeponline/pi-wishcraft` with **HTTP 401**, so npmjs.com carries no matching
107
+ GitHub Actions publisher for this repo. With no publisher and no token, npm
108
+ reports `ENEEDAUTH` — that is what kept v1.18.0–v1.22.0 off the registry.
109
+ Measured from this repository on 2026-11-08:
110
+
111
+ | probe | result |
112
+ |---|---|
113
+ | `npm whoami` with `secrets.NPM_TOKEN` | `chefgroeponline` — valid |
114
+ | `ACTIONS_ID_TOKEN_REQUEST_URL` in a job with `id-token: write` | present |
115
+ | `POST /-/npm/v1/oidc/token/exchange/package/@groeponline%2fpi-wishcraft` | **401** |
116
+
117
+ Two consequences: publishes carry no provenance attestation, and the token has
118
+ to be replaced by hand when it expires. Switching to trusted publishing is a
119
+ one-time npmjs.com change, after which the `NODE_AUTH_TOKEN` env comes out of
120
+ the job:
121
+
122
+ 1. Sign in as the maintainer of `@groeponline/pi-wishcraft` (`chefgroeponline
123
+ <joep@chefgroep.online>` — `npm view @groeponline/pi-wishcraft maintainers`).
124
+ 2. Package → Settings → **Trusted publishing** → add a **GitHub Actions**
125
+ publisher with exactly these values (all fields case-sensitive):
126
+ - Organization or user: `GroepOnline`
127
+ - Repository: `pi-wishcraft`
128
+ - Workflow filename: `release.yml` (with `.yml`)
129
+ - Environment name: empty
130
+ - Allowed actions: **tick `npm publish`** — it is *not* on by default. A
131
+ configuration created after 2026-09-03 starts with staged publishing only,
132
+ so without this tick every `npm publish` is refused. Leave `npm dist-tag`
133
+ off; this repo does not use it.
134
+ 3. Verify before trusting it: the exchange line in the table above must answer
135
+ `200`, not `401`. Only then remove `NODE_AUTH_TOKEN` from the job and let OIDC
136
+ publish.
137
+
138
+ Rules that follow from the current (token) configuration:
139
+
140
+ - The credential must stay in the `publish` job of `release.yml`. Moving
141
+ `npm publish` into a reusable workflow (`workflow_call`) would break the OIDC
142
+ match if trusted publishing is switched on later, because npm validates the
143
+ *calling* workflow's filename.
144
+ - Only GitHub-hosted runners can use OIDC; the token path works anywhere.
145
+ - `npm-publish.sh` publishes with the default `latest` dist-tag, and versions
146
+ only ever increase here, so `npm dist-tag` is deliberately never used.
147
+
148
+ ### When publish fails: `E404 Not Found - PUT .../@groeponline%2fpi-wishcraft`
149
+
150
+ For a scoped package npm answers a PUT with **404, not 403**, when the caller
151
+ may not publish. That means the credential is missing, revoked, read-only or
152
+ (`EOTP`) 2FA-blocked, or does not own the scope. It is not "package not found",
153
+ and it is not a retryable transient. `ENEEDAUTH` instead means npm had no
154
+ credential at all: `NODE_AUTH_TOKEN` was empty and the OIDC exchange refused.
155
+
156
+ `scripts/npm-publish.sh` prints that diagnosis instead of the raw 404. Recovery:
157
+
158
+ 1. Check the credential before the workflow: `npm whoami` against the registry
159
+ with the same token, or rerun the exchange probe from the table above.
160
+ 2. Republish the tags that failed, without cutting new versions. A bare
161
+ dispatch on a `v*` tag ref runs verify + publish for that tag:
162
+
163
+ ```bash
164
+ gh workflow run release.yml --ref v1.19.0 # repeats for each failed tag
165
+ ```
166
+
167
+ Or re-run the failed job directly: `gh run rerun <run-id> --failed`.
168
+ A tag stranded this way keeps its GitHub Release missing until the publish
169
+ run succeeds; `workflow_dispatch` with `inputs.tag` only backfills the
170
+ release assets and never publishes to npm.
171
+ 3. A dispatch runs the workflow file **at that tag**, so a tag whose publish job
172
+ is broken by construction cannot be replayed at all: publish the newer tag
173
+ that carries the fix and leave the version hole, since npm versions need not
174
+ be contiguous. v1.20.0 is such a hole — it was tagged by a publish job that
175
+ called `bun` before installing it.
176
+
177
+ The idempotent skip in `npm-publish.sh` makes a retry safe: a version that is
178
+ already on the registry is skipped, so a partially successful run cannot
179
+ double-publish.
89
180
 
90
181
  The package has no build step — `*.ts` files are shipped directly (`pi.extensions:
91
182
  ["./index.ts"]`), and Pi loads them with TypeScript stripping at runtime.
@@ -96,8 +187,8 @@ After a release, confirm each layer. Example for `0.15.0`:
96
187
 
97
188
  ```bash
98
189
  # 1. Local: typecheck + tests green BEFORE tagging
99
- npm run typecheck # exit 0
100
- npm test # "ℹ pass 170", "ℹ fail 0"
190
+ bun run typecheck # exit 0
191
+ bun run test # "ℹ pass 170", "ℹ fail 0"
101
192
 
102
193
  # 2. Tag pushed
103
194
  git ls-remote --tags origin | grep v0.15.0 # shows v0.15.0 on origin
@@ -142,13 +233,15 @@ is that same tag.
142
233
 
143
234
  ## Versioning
144
235
 
145
- Feature PRs **do not bump** `package.json`. They stay on the last published npm
146
- version (today: `0.19.0`) and append under `## [Unreleased]` in `CHANGELOG.md`.
236
+ Feature PRs **do not bump** `package.json`. They inherit whatever version
237
+ `main` already carries and append under `## [Unreleased]` in `CHANGELOG.md`.
147
238
  The bump happens on `main` in the release workflow, not in the feature PR.
148
239
 
149
240
  Never bump in the same PR as the feature work. Never tag from a stacked
150
- feature branch. `0.19.0` exists only after `main` auto-tags (or a manual
151
- `npm run release` on `main`).
241
+ feature branch. `v<version>` exists only after Promote tags the verified
242
+ candidate (or a manual `bun run release` on `main`). `main` can be ahead of npm
243
+ when a publish fails — compare `npm view @groeponline/pi-wishcraft version`
244
+ with `bun scripts/release.mjs next auto` before assuming they agree.
152
245
 
153
246
  - **patch** (`0.18.0 → 0.18.1`): bug fixes, doc only; escape hatch if 0.19 slips.
154
247
  - **minor** (`0.18.x → 0.19.0`): new segments/presets, features, additive changes.
@@ -1,7 +1,7 @@
1
1
  import { readdirSync } from "node:fs";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
4
- import type { AutocompleteItem, AutocompleteProvider, AutocompleteSuggestions } from "@earendil-works/pi-tui";
4
+ import type { AutocompleteItem, AutocompleteProvider } from "@earendil-works/pi-tui";
5
5
  import { matchHistoryEntries, readGlobalShellHistory, readProjectHistory } from "./history.ts";
6
6
  import type { ExtendedCompletionItem, GhostSuggestion } from "./types.ts";
7
7
 
@@ -365,7 +365,7 @@ function boostValidatedItemsFromGlobalHistory(
365
365
  }
366
366
 
367
367
  export class BashCompletionEngine {
368
- async getGhostSuggestion(line: string, cwd: string, shellPath: string, signal: AbortSignal): Promise<GhostSuggestion | null> {
368
+ async getGhostSuggestion(line: string, cwd: string, shellPath: string, _signal: AbortSignal): Promise<GhostSuggestion | null> {
369
369
  const projectHistoryEntries = readProjectHistory(cwd);
370
370
 
371
371
  if (line.trim().length === 0) {
package/build-stamp.json CHANGED
@@ -1 +1 @@
1
- {"source_sha":"1957ea6e4334e28e25770cba4114e33c69062632"}
1
+ {"source_sha":"a12a4900dbbf32823e1c2c6261c850eb21834d38"}
@@ -0,0 +1,60 @@
1
+ # Domain Docs
2
+
3
+ How the engineering skills should consume this repo's domain documentation when exploring the codebase.
4
+
5
+ ## Before exploring, read these
6
+
7
+ - **`AGENTS.md`** at the repo root — the current domain documentation source with architecture, data flow, and conventions.
8
+ - **`CONTEXT.md`** at the repo root, if it exists (currently absent).
9
+ - **`CONTEXT-MAP.md`** at the repo root if it exists: it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
10
+ - **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
11
+
12
+ If `CONTEXT.md` or `docs/adr/` don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
13
+
14
+ ## File structure
15
+
16
+ Single-context repo (this repo):
17
+
18
+ **Current state:**
19
+ - `AGENTS.md` — Primary domain documentation (architecture, data flow, conventions)
20
+ - `docs/` — Operator guides and design specs (no ADRs currently)
21
+ - `CONTEXT.md` — Not yet created (add via `/domain-modeling` when needed)
22
+
23
+ **Target state (when CONTEXT.md exists):**
24
+
25
+ ```
26
+ /
27
+ ├── AGENTS.md
28
+ ├── CONTEXT.md
29
+ ├── docs/adr/
30
+ │ ├── 0001-event-sourced-orders.md
31
+ │ └── 0002-postgres-for-write-model.md
32
+ └── src/
33
+ ```
34
+
35
+ Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
36
+
37
+ ```
38
+ /
39
+ ├── CONTEXT-MAP.md
40
+ ├── docs/adr/ ← system-wide decisions
41
+ └── src/
42
+ ├── ordering/
43
+ │ ├── CONTEXT.md
44
+ │ └── docs/adr/ ← context-specific decisions
45
+ └── billing/
46
+ ├── CONTEXT.md
47
+ └── docs/adr/
48
+ ```
49
+
50
+ ## Use the glossary's vocabulary
51
+
52
+ When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `AGENTS.md` (current domain source) or `CONTEXT.md` (if it exists). Don't drift to synonyms the glossary explicitly avoids.
53
+
54
+ If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
55
+
56
+ ## Flag ADR conflicts
57
+
58
+ If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
59
+
60
+ > _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_