@groeponline/pi-wishcraft 1.17.0 → 1.24.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 (48) hide show
  1. package/AGENTS.md +42 -22
  2. package/CHANGELOG.md +66 -0
  3. package/CONTRIBUTING.md +12 -1
  4. package/RELEASE.md +171 -49
  5. package/bash-mode/completion.ts +4 -2
  6. package/build-stamp.json +1 -1
  7. package/docs/configuration.md +6 -1
  8. package/docs/design/regression-testing.md +1 -1
  9. package/docs/testing.md +81 -0
  10. package/package.json +17 -10
  11. package/src/config/settings-registry.ts +2 -1
  12. package/src/config/structural-preset-table.ts +0 -1
  13. package/src/config/tokens.ts +21 -3
  14. package/src/config/types.ts +4 -1
  15. package/src/extension/commands/commands.ts +0 -1
  16. package/src/extension/core/peer-guard.ts +5 -0
  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/component.ts +11 -0
  25. package/src/extension/ui/deck/config-diagnostic-lines.ts +0 -6
  26. package/src/extension/ui/ports-panel.ts +0 -1
  27. package/src/extension/ui/token-overlays.ts +1 -1
  28. package/src/extension/welcome/welcome-integration.ts +65 -5
  29. package/src/git/status.ts +4 -0
  30. package/src/i18n/nl.ts +3 -0
  31. package/src/segments/core.ts +0 -1
  32. package/src/segments/system.ts +19 -4
  33. package/src/studio/component.ts +0 -1
  34. package/src/theme/theme.ts +7 -1
  35. package/src/tools/graph.ts +0 -3
  36. package/src/usage/ledger.ts +5 -0
  37. package/src/usage/tps-ring.ts +172 -32
  38. package/src/welcome/banner.ts +14 -0
  39. package/src/welcome/overlay.ts +139 -7
  40. package/src/welcome/quick-launch.ts +44 -0
  41. package/src/welcome/types.ts +13 -0
  42. package/src/welcome/widgets/sessions-widget.ts +13 -9
  43. package/src/welcome/widgets/shortcuts-widget.ts +13 -5
  44. package/src/welcome/widgets/whats-new-widget.ts +16 -3
  45. package/src/working-vibes/frames.ts +81 -5
  46. package/src/working-vibes/index.ts +6 -1
  47. package/src/working-vibes/native-indicator.ts +71 -0
  48. 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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,72 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.24.0] - 2026-11-12
6
+
7
+ ### Fixed
8
+ - **Git probes no longer race interactive git.** Every read-only `git` spawn (`src/git/status.ts`, `bash-mode/completion.ts`) now runs with `GIT_OPTIONAL_LOCKS=0` and `windowsHide: true`, so status polling can't refresh `.git/index` under a real checkout and orphaned `index.lock` files stop bricking the repo.
9
+ - **Think levels high/xhigh/max got their rainbow back.** The thinking segment collapsed all three into the plain `thinking` color; it now uses dedicated `thinkingHigh`/`thinkingXhigh`/`thinkingMax` semantic colors (default `rainbow`, overridable per preset — the `rainbow` ColorValue itself is new).
10
+ - **Token totals prefer the provider-reported `totalTokens`.** The four-field sum undercounted providers that report reasoning/other tokens only in the total.
11
+
12
+
13
+ ## [1.23.0] - 2026-11-08
14
+
15
+ ### Fixed
16
+ - **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.
17
+
18
+ ### Changed
19
+ - **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)
20
+
21
+ ## [1.22.0] - 2026-11-05
22
+
23
+ ### Changed
24
+ - **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.
25
+
26
+ ## [1.21.0] - 2026-11-04
27
+
28
+ ### Fixed
29
+ - **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.
30
+
31
+ ## [1.20.0] - 2026-11-04
32
+
33
+ ### Fixed
34
+ - **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.
35
+ - **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.
36
+ - **`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`).
37
+
38
+ ### Changed
39
+ - **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.
40
+ - **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.
41
+ - **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.
42
+ - **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.
43
+ - **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.
44
+ - **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.
45
+ - `audit-baseline.json` and `.oxlintrc.json` are excluded from the published tarball; `bun.lock` never ships because `files` does not match it.
46
+
47
+ ## [1.19.0] - 2026-11-04
48
+
49
+ ### Fixed
50
+ - **`/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.
51
+ - **`/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.
52
+ - **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.
53
+ - **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.
54
+ - **`/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.
55
+ - 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.
56
+ - `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.
57
+
58
+ ### Added
59
+ - Working-indicator styles `lantern` and `braille` (settings registry choices are now derived from `workingIndicatorStyles()` so the two cannot drift).
60
+ - 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.
61
+ - 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.
62
+
63
+ ## [1.18.0] - 2026-11-03
64
+
65
+ ### Security
66
+ - 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.
67
+
68
+ ### Fixed
69
+ - `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.
70
+
5
71
  ## [1.17.0] - 2026-10-27
6
72
 
7
73
  ### Fixed
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,136 @@ 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
+ | `npm publish` with that token (v1.23.0, run 42464125440) | **EOTP** — one-time password required |
117
+
118
+ **The token authenticates but cannot write.** It is a *Publish* token on an
119
+ account with 2FA enabled, so npm demands an OTP for `npm publish` and a CI
120
+ runner can never answer one.
121
+
122
+ **Resolved 2026-11-09 03:58 UTC.** The org `NPM_TOKEN` secret was replaced with a
123
+ granular token that has *Bypass 2FA*; run 42559311821 (dispatch on `v1.23.0`)
124
+ then published `1.23.0` and created its GitHub Release. No workflow change was
125
+ needed — `NODE_AUTH_TOKEN` still publishes. `v1.18.0`–`v1.22.0` stay unpublished:
126
+ their tag refs carry unpinned `uses: actions/...@v7` and the org policy rejects
127
+ them, so they cannot be replayed without rewriting tags. `1.23.0` supersedes them.
128
+
129
+ The two routes that were available, for the record:
130
+
131
+ - **Token route (unblocks v1.23.0 as tagged):** Access Tokens → Generate New
132
+ Token → **Granular**, read+write on `@groeponline` (or a classic
133
+ **Automation** token), with **Bypass 2FA** enabled; store it as the org
134
+ `NPM_TOKEN` secret. Then replay the tag:
135
+ `gh workflow run release.yml --ref v1.23.0` — that same job also creates the
136
+ missing GitHub Release.
137
+ - **OIDC route (no credential at all):** configure the trusted publisher below
138
+ and drop the `NODE_AUTH_TOKEN` env from the `publish` job. A tag cut *before*
139
+ that change still carries the token env and cannot be replayed, so this route
140
+ publishes the next version and leaves the hole.
141
+
142
+ Switching to trusted publishing is a one-time npmjs.com change, after which the
143
+ `NODE_AUTH_TOKEN` env comes out of the job:
144
+
145
+ 1. Sign in as the maintainer of `@groeponline/pi-wishcraft` (`chefgroeponline
146
+ <joep@chefgroep.online>` — `npm view @groeponline/pi-wishcraft maintainers`).
147
+ 2. Package → Settings → **Trusted publishing** → add a **GitHub Actions**
148
+ publisher with exactly these values (all fields case-sensitive):
149
+ - Organization or user: `GroepOnline`
150
+ - Repository: `pi-wishcraft`
151
+ - Workflow filename: `release.yml` (with `.yml`)
152
+ - Environment name: empty
153
+ - Allowed actions: **tick `npm publish`** — it is *not* on by default. A
154
+ configuration created after 2026-09-03 starts with staged publishing only,
155
+ so without this tick every `npm publish` is refused. Leave `npm dist-tag`
156
+ off; this repo does not use it.
157
+ 3. Verify before trusting it: the exchange line in the table above must answer
158
+ `200`, not `401`. Only then remove `NODE_AUTH_TOKEN` from the job and let OIDC
159
+ publish.
160
+
161
+ Rules that follow from the current (token) configuration:
162
+
163
+ - The credential must stay in the `publish` job of `release.yml`. Moving
164
+ `npm publish` into a reusable workflow (`workflow_call`) would break the OIDC
165
+ match if trusted publishing is switched on later, because npm validates the
166
+ *calling* workflow's filename.
167
+ - Only GitHub-hosted runners can use OIDC; the token path works anywhere.
168
+ - `npm-publish.sh` publishes with the default `latest` dist-tag, and versions
169
+ only ever increase here, so `npm dist-tag` is deliberately never used.
170
+
171
+ ### When publish fails: `E404 Not Found - PUT .../@groeponline%2fpi-wishcraft`
172
+
173
+ For a scoped package npm answers a PUT with **404, not 403**, when the caller
174
+ may not publish. That means the credential is missing, revoked, read-only or
175
+ does not own the scope. It is not "package not found", and it is not a
176
+ retryable transient. Two neighbours are distinct and have their own fix:
177
+
178
+ - `ENEEDAUTH` — npm had no credential at all: `NODE_AUTH_TOKEN` was empty and
179
+ the OIDC exchange refused.
180
+ - `EOTP` — the credential authenticates but the account requires a one-time
181
+ password for writes, which CI cannot supply. This is what v1.23.0 hit. The
182
+ token needs 2FA bypass (granular token with *Bypass 2FA*, or a classic
183
+ Automation token); retrying with the same token only burns another run.
184
+
185
+ `scripts/npm-publish.sh` prints that diagnosis instead of the raw 404. Recovery:
186
+
187
+ 1. Check the credential before the workflow: `npm whoami` against the registry
188
+ with the same token, or rerun the exchange probe from the table above.
189
+ 2. Republish the tags that failed, without cutting new versions. A bare
190
+ dispatch on a `v*` tag ref runs verify + publish for that tag:
191
+
192
+ ```bash
193
+ gh workflow run release.yml --ref v1.19.0 # repeats for each failed tag
194
+ ```
195
+
196
+ Or re-run the failed job directly: `gh run rerun <run-id> --failed`.
197
+ A tag stranded this way keeps its GitHub Release missing until the publish
198
+ run succeeds; `workflow_dispatch` with `inputs.tag` only backfills the
199
+ release assets and never publishes to npm.
200
+ 3. A dispatch runs the workflow file **at that tag**, so a tag whose publish job
201
+ is broken by construction cannot be replayed at all: publish the newer tag
202
+ that carries the fix and leave the version hole, since npm versions need not
203
+ be contiguous. v1.20.0 is such a hole — it was tagged by a publish job that
204
+ called `bun` before installing it.
205
+
206
+ The idempotent skip in `npm-publish.sh` makes a retry safe: a version that is
207
+ already on the registry is skipped, so a partially successful run cannot
208
+ double-publish.
89
209
 
90
210
  The package has no build step — `*.ts` files are shipped directly (`pi.extensions:
91
211
  ["./index.ts"]`), and Pi loads them with TypeScript stripping at runtime.
@@ -96,8 +216,8 @@ After a release, confirm each layer. Example for `0.15.0`:
96
216
 
97
217
  ```bash
98
218
  # 1. Local: typecheck + tests green BEFORE tagging
99
- npm run typecheck # exit 0
100
- npm test # "ℹ pass 170", "ℹ fail 0"
219
+ bun run typecheck # exit 0
220
+ bun run test # "ℹ pass 170", "ℹ fail 0"
101
221
 
102
222
  # 2. Tag pushed
103
223
  git ls-remote --tags origin | grep v0.15.0 # shows v0.15.0 on origin
@@ -142,13 +262,15 @@ is that same tag.
142
262
 
143
263
  ## Versioning
144
264
 
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`.
265
+ Feature PRs **do not bump** `package.json`. They inherit whatever version
266
+ `main` already carries and append under `## [Unreleased]` in `CHANGELOG.md`.
147
267
  The bump happens on `main` in the release workflow, not in the feature PR.
148
268
 
149
269
  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`).
270
+ feature branch. `v<version>` exists only after Promote tags the verified
271
+ candidate (or a manual `bun run release` on `main`). `main` can be ahead of npm
272
+ when a publish fails — compare `npm view @groeponline/pi-wishcraft version`
273
+ with `bun scripts/release.mjs next auto` before assuming they agree.
152
274
 
153
275
  - **patch** (`0.18.0 → 0.18.1`): bug fixes, doc only; escape hatch if 0.19 slips.
154
276
  - **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
 
@@ -246,6 +246,8 @@ function runGit(args: string[], cwd: string): string[] {
246
246
  cwd,
247
247
  encoding: "utf8",
248
248
  stdio: ["ignore", "pipe", "ignore"],
249
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
250
+ windowsHide: true,
249
251
  });
250
252
  if (result.status !== 0 || !result.stdout) return [];
251
253
  return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
@@ -365,7 +367,7 @@ function boostValidatedItemsFromGlobalHistory(
365
367
  }
366
368
 
367
369
  export class BashCompletionEngine {
368
- async getGhostSuggestion(line: string, cwd: string, shellPath: string, signal: AbortSignal): Promise<GhostSuggestion | null> {
370
+ async getGhostSuggestion(line: string, cwd: string, shellPath: string, _signal: AbortSignal): Promise<GhostSuggestion | null> {
369
371
  const projectHistoryEntries = readProjectHistory(cwd);
370
372
 
371
373
  if (line.trim().length === 0) {
package/build-stamp.json CHANGED
@@ -1 +1 @@
1
- {"source_sha":"0c1a77045865dbb0dd6138ac1d0b381c067d0395"}
1
+ {"source_sha":"1e85fa41738909a1dd3d098ad8ef0986ae76a298"}
@@ -210,8 +210,11 @@ The indicator is intentionally separate from the structural appearance motion: i
210
210
  | `pulse` | expanding/contracting center | clear activity at a glance |
211
211
  | `bar` | travelling block across a track | high-information dashboards |
212
212
  | `ascii` | portable `[=]`, `[-]`, `[.]` frames | SSH, basic terminals, and logs |
213
+ | `lantern` | rising block ramp (▁→█) | the signature look, ambient sessions |
214
+ | `braille` | classic braille spinner | compact terminals and log capture |
215
+ | `motion` | frames from the Motion Catalog (the motion assigned to `thinking`) at that definition's own `generator.intervalMs` | matching the working spinner to the rest of the motion language |
213
216
 
214
- Configure it under `wishcraft.workingIndicatorStyle`; supported values are `dots` (default), `pulse`, `bar`, and `ascii`. Motion accessibility settings still apply: reduced/off motion uses a static frame.
217
+ Configure it under `wishcraft.workingIndicatorStyle`; supported values are `dots` (default), `pulse`, `bar`, `ascii`, `lantern`, `braille`, and `motion`. The list in the settings UI is derived from `workingIndicatorStyles()`, so it cannot drift from what the renderer accepts. Motion accessibility settings still apply: reduced/off motion uses a static frame, functional motion alternates two frames, and ASCII glyph mode forces the ASCII set — `motion` falls back to `lantern` when its motion id is unknown.
215
218
 
216
219
  ```json
217
220
  {
@@ -506,6 +509,8 @@ Four questions — language, status preset, motion level, welcome overlay — th
506
509
 
507
510
  On the very first launch the welcome panel shows three next steps under **Getting started** instead of a changelog wall. Later launches show the usual changelog delta under **What's new**.
508
511
 
512
+ The panel is a cockpit, not a splash screen. `Tab`/`Shift+Tab` step between **What's new**, **Recent Crafts** and **Quick Launch**, ↑/↓ move inside the focused section (the selected changelog bullet expands across wrapped lines instead of being truncated), and `Enter` — only after navigation has paused the countdown — primes the prompt for the focused quick-launch row (`# ` for an idea, `! ` for a sticky bash session). `Recent Crafts` is read-only: Pi exposes no session-resume handle. No printable key is ever captured by the overlay, so the first letter of the prompt you start typing still dismisses it. With `wishcraft.welcome.animateLantern` on, the lantern keeps burning on the ambient heartbeat after the boot reveal instead of freezing on its last frame.
513
+
509
514
  ## Segment options: two accepted shapes
510
515
 
511
516
  Segment options may be written either hand-edited at the top level of `powerline`, or nested under `powerline.segmentOptions` (the shape the settings UI writes):
@@ -40,7 +40,7 @@ Wishcraft establishes a multi-tiered test strategy that verifies behavior withou
40
40
 
41
41
  1. **No Headless TUI Requirement**: Core algorithms must never instantiate `ctx.ui` or direct terminal stdout in tests. Renderers emit plain string buffers or token trees.
42
42
  2. **Deterministic Time Control**: The `MotionScheduler` accepts an injected virtual clock or step function for precise, millisecond-accurate timeline assertions.
43
- 3. **Circular Dependency Prevention**: Verified automatically on every build via `npx madge --circular src/`.
43
+ 3. **Circular Dependency Prevention**: Verified automatically on every build via `bun run circular`.
44
44
  4. **Strict TypeScript Checking**: `tsc --noEmit` runs under strict mode with zero implicit `any`.
45
45
 
46
46
  ---