pier-setup 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -7
- package/install.mjs +56 -35
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -26,11 +26,18 @@ pier ships as **two halves**, installed separately:
|
|
|
26
26
|
- **Todo loop**: `todo_write` with full-replace semantics, session JSONL as source of truth, correct branch rollback; live pane-title projection `▶i ○p ■b ✓c (N/M) · current task`; TUI widget is an activity-anchored window (the in_progress entry and its surrounding context stay visible as work moves down the list); `/todos` command (including unblock)
|
|
27
27
|
- **Todo anti-freeze**: an all-completed list unchanged for ≥6 turns flips the read hook from recitation to a rewrite warning; after ≥1h it archives — first offering one rewrite window with old entries as reference, then (if ignored) a final notice that clears the list (rm persisted to JSONL; the empty guard takes over — multi-step work must re-track); `/todos` still shows history, session JSONL untouched
|
|
28
28
|
- **Interactive subagents**: every subagent = an isolated pi session in its own pane (separate context window); **a human can enter that pane and talk to it directly** (fix bugs, take over, answer ask_user_question)
|
|
29
|
-
- **
|
|
29
|
+
- **Subagent output preview**: `subagent(action: "output", agentId)` returns what a running background subagent has printed since the previous call — an in-memory per-pane cursor computes the delta (`agent.read`, falling back to `pane.read` on older herdr), and the result carries status, revision, truncation and a `restart` flag for scrolled/cleared buffers, so the master can notice a stuck worker instead of waiting for settlement
|
|
30
|
+
- **Soft write-locks**: per-pane lock beacons on `write`/`edit` paths; cross-pane conflicts warn (default) or block (`PI_HERDR_WRITE_LOCK=1`). The warning lists every holder; `/locks` is the human view of the same table (`herdr agent list` exposes the raw tokens to agents)
|
|
30
31
|
- **Role profiles**: built-in `master` / `worker-default`; custom roles as `.pi-herdr/roles/<name>.json`; toolset converges per role (deny rules are not bypassable)
|
|
31
32
|
- **Human gate**: subagent `ask_user_question` → sidebar blocked marker + notification; manual takeover (ESC-interrupt then typing) is heuristically detected so the master pauses/returns management automatically
|
|
33
|
+
- **Ask picker**: a `multi: true` question renders as an interactive toggle list (space toggles a row, `a` toggles all, enter confirms, esc declines, live selection count) with the free-text row pinned last; `allowOther: false` gives pure choice. The typed-index prompt remains the fallback for RPC mode and older pi
|
|
34
|
+
- **Blocking dialogs mark the pane**: pi's `ui_prompt_start` / `ui_prompt_end` events cover the inherently blocking `ctx.ui` dialogs (`select` / `confirm` / `input` / `editor`), so a dialog from *any* extension marks the pane blocked and emits the `herdr:blocked` edge; nested gates coalesce, and the ask tool no longer needs its 5s refresh heartbeat. `custom` is deliberately excluded — pi routes resident overlays through it too (pier's own slim-frame overlay never calls `done()`), which used to leave a working pane marked blocked for the whole session
|
|
35
|
+
- **Role gate stops early**: a denied worker tool call returns `terminate`, so a batch whose results are all terminating ends without another model round trip
|
|
36
|
+
- **Transcript cards**: pier's own session entries (todo edits, subagent/terminal registries, role manifests, soft approvals) and its reminder messages render as compact cards instead of raw JSON — entry/message renderers, no pi-tui dependency
|
|
32
37
|
- **Isolated worktree subagents (`isolate`)**: heavy parallel writers get a fresh git worktree (branch `pier/<slug>` from your HEAD under `~/.herdr/worktrees/<repo>/`) with commit discipline in the prompt; settlement carries a diff summary (commits since base, files changed, uncommitted count); merge with `git merge --no-ff` and the worktree auto-removes once merged and clean — the branch stays for audit
|
|
33
|
-
- **
|
|
38
|
+
- **Worktree collection is ownership-scoped**: only worktrees this session registered are collected, and the running process's own directory is never a candidate — the earlier prefix match on `refs/heads/pier/` could delete a parallel session's worktree out from under it. Sweeping branches the session never registered is opt-in (`PIER_ISOLATE_SWEEP_ORPHANS=1`)
|
|
39
|
+
- **Ops dashboard + sidebar view (herdr 0.9)**: `herdr plugin pane open --plugin pier.workbench --entrypoint dashboard` opens a live pane/tab/agent board (roles, states, todo progress); the plugin also registers a `Pier` sidebar agent view that lists every detected agent (no harness filter) with attention-first ordering
|
|
40
|
+
- **Long-task lifecycle (01a03c0d review)**: observation timeout is now an inactivity budget (working slices renew it — healthy >10min tasks are no longer killed); follow_up messages deliver via steer at tool-call gaps (supplementary contracts arrive in seconds, not after the whole run); GC waits for the settlement notice to be delivered before closing panes; takeover detection attributes recent machine injections first; ledger rows carry a `via` tag, closed rows inherit their outcome, zombie running rows are swept on startup; SUBS snapshots are hash-gated. Tunables: `PIER_SUBAGENT_TIMEOUT_MS` (inactivity ms), `PIER_SETTLEMENT_WINDOW_MS` (ms), `PIER_OBSERVATION_WINDOW_MS` (ms)
|
|
34
41
|
- **Settlement notice folding**: subagent settlements no longer flood back when a long main run ends — they inject at turn gaps, up to 3 shown, the rest folded with pointers
|
|
35
42
|
|
|
36
43
|
## Installation
|
|
@@ -38,8 +45,8 @@ pier ships as **two halves**, installed separately:
|
|
|
38
45
|
### Requirements
|
|
39
46
|
|
|
40
47
|
- Node ≥ 22
|
|
41
|
-
- pi ≥ 0.84 (`@earendil-works/pi-coding-agent`)
|
|
42
|
-
- herdr ≥ 0.
|
|
48
|
+
- pi ≥ 0.84.4 (`@earendil-works/pi-coding-agent`)
|
|
49
|
+
- herdr ≥ 0.9.0 (macOS / Linux / Windows; Windows is preview beta)
|
|
43
50
|
|
|
44
51
|
### One-shot install (recommended)
|
|
45
52
|
|
|
@@ -147,16 +154,80 @@ them automatically; manually, copy `packages/pier-workbench/scripts/boot-config.
|
|
|
147
154
|
packages/
|
|
148
155
|
pier-ext/ # pi extension (npm: pi-pier): todo/subagent tools, herdr client, vocab authority, skill
|
|
149
156
|
pier-workbench/ # herdr plugin (pier.workbench): workspace bootstrap + blocked notify + heat layout
|
|
150
|
-
docs/ # install guide, role profile docs, sidebar role config
|
|
157
|
+
docs/ # install guide, configuration overview, role profile docs, sidebar role config
|
|
151
158
|
```
|
|
152
159
|
|
|
153
160
|
## Testing
|
|
154
161
|
|
|
155
162
|
```sh
|
|
156
163
|
npm install --ignore-scripts
|
|
157
|
-
npm test # node --test,
|
|
164
|
+
npm test # node --test, 809 unit tests (planner / todo replay / anti-freeze staleness / session tail / GC / lifecycle / renderers / ask picker / subagent output / jev decision layer: diagnostic gate, notice ranking, excerpt windows)
|
|
158
165
|
```
|
|
159
166
|
|
|
167
|
+
## Configuration
|
|
168
|
+
|
|
169
|
+
Runtime policies and operational limits are centralized in `runtime-policy.ts` (with terminal read limits in `terminal.ts`). All timeouts use millisecond units unless specified otherwise:
|
|
170
|
+
|
|
171
|
+
| Variable | Default | Unit | Purpose |
|
|
172
|
+
|---|---|---|---|
|
|
173
|
+
| `PIER_SUBAGENT_TIMEOUT_MS` | `600000` | ms | Subagent inactivity budget / overall timeout before forced termination |
|
|
174
|
+
| `PIER_SETTLEMENT_WINDOW_MS` | `60000` | ms | Settlement notice window, machine injection grace period, and takeover idle threshold |
|
|
175
|
+
| `PIER_OBSERVATION_WINDOW_MS` | `30000` | ms | Post-settle observation window before auto-consuming finished subagents |
|
|
176
|
+
| `PIER_FOREGROUND_PATIENCE_MS` | `300000` | ms | Foreground execution patience before auto-demoting a subagent to background |
|
|
177
|
+
| `PIER_GC_TICK_MS` | `30000` | ms | Subagent garbage collection ticker interval |
|
|
178
|
+
| `PIER_POLL_INTERVAL_MS` | `30000` | ms | State observation polling interval for subagent state transitions |
|
|
179
|
+
| `PIER_READY_TIMEOUT_MS` | `90000` | ms | Subagent pane pipe readiness wait (exponential backoff; a pane that exited fails immediately with its last output attached) |
|
|
180
|
+
| `PIER_SESSION_TTL_SECONDS` | `600` | s | Session retention TTL after subagent exit before GC cleanup |
|
|
181
|
+
| `PIER_GIT_TIMEOUT_MS` | `10000` | ms | Execution timeout for git operations (worktree creation, diff summary, cleanup) |
|
|
182
|
+
| `PIER_FOCUS_POLL_MS` | `1500` / `0` | ms | Focus sampling for heat layout (`0` disables). Default 1500ms on Herdr <0.9.1; 0 (event-first) on 0.9.1+ |
|
|
183
|
+
| `PIER_TERM_READ_MAX` | `8000` | chars | Maximum terminal buffer characters read per operation |
|
|
184
|
+
| `PIER_TERM_IDLE_MS` | `1800000` | ms | Idle time before pier nudges about an open terminal |
|
|
185
|
+
| `PIER_TODO_GRACE_MS` | `30000` | ms | Settle grace before the unfinished-todo reminder |
|
|
186
|
+
| `PIER_TRACE` | – | flag | Write diagnostics (tool renderers, swallowed errors) to stderr |
|
|
187
|
+
| `PIER_JEV_ENABLE` | `0` | flag | Jev decision layer (TypeSafe System One classification) master switch; every call site fails open — see `docs/rfc-jev-integration.md` |
|
|
188
|
+
| `PIER_JEV_LOG` | `0` | flag | Write `efficiency-logs/jev.jsonl` (metadata + hashes only, never bodies) |
|
|
189
|
+
| `PIER_JEV_API_KEY` | – | – | API key; precedence env > config `jev.apiKey` > `TYPESAFE_API_KEY` |
|
|
190
|
+
| `PIER_JEV_MODEL` | `jev-1.13.0` | – | Pinned versioned model id (aliases drift silently and skew tuned thresholds) |
|
|
191
|
+
| `PIER_JEV_TIMEOUT_MS` | `2000` | ms | Total per-call budget (AbortController hard kill) |
|
|
192
|
+
| `PIER_JEV_MIN_CONFIDENCE` | `0.6` | – | Minimum Choice/Score confidence to adopt an answer |
|
|
193
|
+
| `PIER_JEV_BASE_URL` | – | – | API root override (relay/gateway) |
|
|
194
|
+
|
|
195
|
+
**Naming:** `PIER_*` is the canonical namespace for pier options; the historical `PI_HERDR_*` spelling
|
|
196
|
+
of the same knob is still read as an alias (an empty value counts as unset). Names handed to child
|
|
197
|
+
processes stay as they are (`PI_HERDR_SUBAGENT`, `PI_HERDR_ROLE_MANIFEST`, `PI_HERDR_TUI`,
|
|
198
|
+
`PI_HERDR_META_KEY`), because renaming them would split a running worker from its parent.
|
|
199
|
+
`/pier-config doctor` lists every option with its effective value and where it came from, plus the
|
|
200
|
+
errors pier deliberately swallowed this session.
|
|
201
|
+
|
|
202
|
+
**Focus heat:** Herdr 0.9.0 resolved mouse focus in the client and did not deliver `pane.focused`
|
|
203
|
+
to plugins, so each pane sampled `layout.export → focused_pane_id` and replayed the workbench
|
|
204
|
+
event. Herdr 0.9.1+ delivers `pane.focused` natively, so the poller defaults off. Override with
|
|
205
|
+
`PIER_FOCUS_POLL_MS`; `PIER_WORKBENCH_ROOT` points at a relocated plugin checkout.
|
|
206
|
+
|
|
207
|
+
### Efficiency mechanisms (D100–D103, opt-in)
|
|
208
|
+
|
|
209
|
+
Three token-saving mechanisms ship **disabled by default** and are safe to try one at a time:
|
|
210
|
+
|
|
211
|
+
- **ObservationPack** — large tool outputs are projected as placeholders after the first `fullSends` requests, with paged retrieval via `obs_recall` (session JSONL stays untouched).
|
|
212
|
+
- **EPR** — long diagnostic (`bash` test/build) logs are reduced in-process to a byte-verified evidence receipt; the raw log is archived first and stays readable.
|
|
213
|
+
- **OCC** — `todo_write` boundaries drive pi's native compaction under a KV-cache cost model, carrying unfinished tasks across the compaction.
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
PI_HERDR_OBS_PACK_ENABLE=1 PI_HERDR_OBS_PACK_LOG=1 pi # start here
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Full quick start, what to watch in `efficiency-logs/*.jsonl`, rollback steps and a feedback template: **[docs/efficiency-trial.md](docs/efficiency-trial.md)**.
|
|
220
|
+
|
|
221
|
+
Everything above (plus roles, pi settings, boot-config and the `PIER_*`/`PI_HERDR_*` env knobs) is also inspectable in-session:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
/pier-config # 5-plane index; hands a guided change to the agent
|
|
225
|
+
/pier-config show all # effective value + source (env > workspace > user > default) per key
|
|
226
|
+
/pier-config check # validation across planes · /pier-config doc → written report
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Read-only by design — see **[docs/configuration.md](docs/configuration.md)** for the plane map and the "which plane do I edit?" table.
|
|
230
|
+
|
|
160
231
|
## Design principles
|
|
161
232
|
|
|
162
233
|
- **Session JSONL is the single source of truth**: todos, delegations, and registries replay from session branches — restart and branch switches stay correct
|
|
@@ -170,6 +241,6 @@ MIT
|
|
|
170
241
|
|
|
171
242
|
---
|
|
172
243
|
|
|
173
|
-
> 💡 **For contributors**: `.gitignore` is
|
|
244
|
+
> 💡 **For contributors**: `.gitignore` is tracked in the repository. Note that `packages/pier-workbench/scripts/boot-config.json` is machine-local (see the `.example.json` template), and `docs/research/` contains local research notes ignored by `.gitignore`.
|
|
174
245
|
>
|
|
175
246
|
> **Naming convention**: the brand is **pier** (repo/packages/plugin); runtime protocol identifiers keep the **`pi-herdr`** prefix (`.pi-herdr/roles/` dir, `pi-herdr.subs` session custom entries, `~/.pi/agent/herdr-pi/roles/` user dir) — they persist with user sessions/config files, renaming would break existing data, so they are the compatibility layer.
|
package/install.mjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* pier
|
|
3
|
+
* pier one-shot installer / uninstaller / updater / version inspector (cross-platform: macOS / Linux / Windows).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* node install.mjs install [--dev] [--hmr-dev] [--force] #
|
|
7
|
-
* node install.mjs update [--dev] [--hmr-dev] #
|
|
8
|
-
* node install.mjs version [--json] #
|
|
9
|
-
* node install.mjs uninstall [--dev] [--purge] #
|
|
10
|
-
* node install.mjs --prepare # npm prepare
|
|
5
|
+
* Usage:
|
|
6
|
+
* node install.mjs install [--dev] [--hmr-dev] [--force] # Install (default command, 'install' can be omitted)
|
|
7
|
+
* node install.mjs update [--dev] [--hmr-dev] # Refresh both halves in place to latest release (no prior uninstall)
|
|
8
|
+
* node install.mjs version [--json] # Local vs npm latest
|
|
9
|
+
* node install.mjs uninstall [--dev] [--purge] # Uninstall; --purge deletes boot-config.json as well
|
|
10
|
+
* node install.mjs --prepare # npm prepare: hooksPath + repo root bin link
|
|
11
11
|
* node install.mjs --help
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Modes:
|
|
14
|
+
* User mode (default): pi install npm:pi-pier
|
|
15
15
|
* + herdr plugin install July24/pier/packages/pier-workbench --yes
|
|
16
|
-
* boot-config.json
|
|
17
|
-
* pi-pier
|
|
18
|
-
* --dev
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
16
|
+
* boot-config.json written to herdr plugin config-dir; extPath points to pi-installed
|
|
17
|
+
* pi-pier (~/.pi/agent/npm/...), not the repo path inside pier-setup package.
|
|
18
|
+
* --dev Development mode: pi install <repo>/packages/pier-ext + herdr plugin link <repo>/packages/pier-workbench
|
|
19
|
+
* (linked directory is live; code edits take effect immediately; update only rewrites boot-config).
|
|
20
|
+
* Distribution specs can be overridden via --pi-spec= / --herdr-spec= (for npm publish or fork scenarios).
|
|
21
|
+
* Failure semantics: Each step provides manual equivalent command; step failure does not abort reporting (exitCode=1).
|
|
22
22
|
*/
|
|
23
23
|
import { execFileSync } from 'node:child_process';
|
|
24
24
|
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
@@ -66,17 +66,17 @@ const command = COMMANDS.includes(argv[0]) ? argv.shift() : null;
|
|
|
66
66
|
const flags = new Set(argv.filter((a) => (a.startsWith('--') && !a.includes('=')) || a === '-h' || a === '-v'));
|
|
67
67
|
const optValue = (name) => argv.find((a) => a.startsWith(`--${name}=`))?.split('=').slice(1).join('=') ?? null;
|
|
68
68
|
const dev = flags.has('--dev');
|
|
69
|
-
const PI_SPEC = optValue('pi-spec') ?? 'npm:pi-pier'; //
|
|
69
|
+
const PI_SPEC = optValue('pi-spec') ?? 'npm:pi-pier'; // User mode defaults to npm release; --pi-spec=git:github.com/July24/pier tracks repo main
|
|
70
70
|
const HERDR_SPEC = optValue('herdr-spec') ?? 'July24/pier/packages/pier-workbench';
|
|
71
71
|
|
|
72
72
|
const log = (m) => console.log(m);
|
|
73
73
|
const die = (m) => { console.error(`✗ ${m}`); process.exit(1); };
|
|
74
74
|
|
|
75
|
-
/* ──
|
|
75
|
+
/* ── Utility functions ────────────────────────────────────────────── */
|
|
76
76
|
function which(bin) {
|
|
77
77
|
try {
|
|
78
78
|
const out = execFileSync(IS_WIN ? 'where' : 'which', [bin], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
79
|
-
// where
|
|
79
|
+
// 'where' may return multiple lines (one per PATH hit); pick the first existing line
|
|
80
80
|
for (const line of out.split('\n').map((l) => l.trim()).filter(Boolean)) {
|
|
81
81
|
if (existsSync(line)) return line;
|
|
82
82
|
}
|
|
@@ -87,9 +87,9 @@ function which(bin) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function run(cmd, args, opts = {}) {
|
|
90
|
-
// win32: npm
|
|
91
|
-
//
|
|
92
|
-
// POSIX
|
|
90
|
+
// win32: npm global bin is a .cmd shim; spawning directly without shell triggers ENOENT/EINVAL, must run via cmd.exe;
|
|
91
|
+
// also when shell:true, Node concatenates args with bare spaces (unquoted), breaking path args with spaces, so pre-join and quote.
|
|
92
|
+
// POSIX uses native branch, preserving execFileSync(cmd, args) semantics.
|
|
93
93
|
if (IS_WIN) {
|
|
94
94
|
const cmdline = [cmd, ...args]
|
|
95
95
|
.map((a) => (/[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a))
|
|
@@ -141,50 +141,50 @@ function npmLatest(name) {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
/**
|
|
144
|
+
/** Herdr plugin config directory (boot-config destination in user mode; semantic path exists once plugin is registered). */
|
|
145
145
|
function herdrConfigDir() {
|
|
146
146
|
try { return run('herdr', ['plugin', 'config-dir', 'pier.workbench']); } catch { return null; }
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/* ──
|
|
149
|
+
/* ── Environment check (pre-install) ────────────────────────────────── */
|
|
150
150
|
function checkEnv() {
|
|
151
151
|
const [major] = process.versions.node.split('.').map(Number);
|
|
152
152
|
if (major < 22) die(`node ≥ 22 required, found ${process.versions.node}`);
|
|
153
153
|
log(`✓ node ${process.versions.node}`);
|
|
154
154
|
const piVersion = (() => { try { return parseVersion(run('pi', ['--version'])); } catch { return null; } })();
|
|
155
|
-
if (!piVersion || !geVersion(piVersion, [0, 84,
|
|
156
|
-
die(`pi ≥ 0.84 required${piVersion ? `, found ${piVersion.join('.')}` : ' (pi not found or not runnable)'}. Install: npm i -g @earendil-works/pi-coding-agent`);
|
|
155
|
+
if (!piVersion || !geVersion(piVersion, [0, 84, 4])) {
|
|
156
|
+
die(`pi ≥ 0.84.4 required${piVersion ? `, found ${piVersion.join('.')}` : ' (pi not found or not runnable)'}. Install: npm i -g @earendil-works/pi-coding-agent`);
|
|
157
157
|
}
|
|
158
158
|
log(`✓ pi ${piVersion.join('.')}`);
|
|
159
159
|
const herdrVersion = (() => { try { return parseVersion(run('herdr', ['--version'])); } catch { return null; } })();
|
|
160
|
-
if (!herdrVersion || !geVersion(herdrVersion, [0,
|
|
161
|
-
die(`herdr ≥ 0.
|
|
160
|
+
if (!herdrVersion || !geVersion(herdrVersion, [0, 9, 0])) {
|
|
161
|
+
die(`herdr ≥ 0.9.0 required${herdrVersion ? `, found ${herdrVersion.join('.')}` : ' (herdr not found)'}. Install: https://herdr.dev/`);
|
|
162
162
|
}
|
|
163
163
|
log(`✓ herdr ${herdrVersion.join('.')}`);
|
|
164
164
|
return { piVersion, herdrVersion };
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
/** pi
|
|
167
|
+
/** pi extension source: user mode uses git/npm spec, dev mode uses local directory. */
|
|
168
168
|
const piSource = () => (dev ? EXT_DIR : PI_SPEC);
|
|
169
169
|
|
|
170
|
-
/* ── boot-config
|
|
170
|
+
/* ── boot-config detection and writing ────────────────────────────── */
|
|
171
171
|
function probePiRuntime() {
|
|
172
172
|
const piBin = which('pi');
|
|
173
|
-
// bin
|
|
173
|
+
// bin is a node shim: realpath resolves to .../pi-coding-agent/dist/cli.js (npm layout, isomorphic win/posix)
|
|
174
174
|
if (piBin) {
|
|
175
175
|
try {
|
|
176
176
|
const real = realpathSync(piBin);
|
|
177
177
|
if (real.endsWith('cli.js') && existsSync(real)) {
|
|
178
178
|
return { piCli: real, piNode: process.execPath };
|
|
179
179
|
}
|
|
180
|
-
} catch { /*
|
|
180
|
+
} catch { /* fall back to fallback */ }
|
|
181
181
|
}
|
|
182
|
-
// npm root -g
|
|
182
|
+
// npm root -g fallback: when pi is not in PATH (e.g. nvm shim), construct from global npm layout
|
|
183
183
|
try {
|
|
184
184
|
const gRoot = run('npm', ['root', '-g']);
|
|
185
185
|
const cand = join(gRoot, '@earendil-works', 'pi-coding-agent', 'dist', 'cli.js');
|
|
186
186
|
if (existsSync(cand)) return { piCli: cand, piNode: process.execPath };
|
|
187
|
-
} catch { /* npm
|
|
187
|
+
} catch { /* npm unavailable */ }
|
|
188
188
|
return null;
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -192,6 +192,25 @@ function piAgentDir() {
|
|
|
192
192
|
return process.env.PI_CODING_AGENT_DIR || join(homedir(), '.pi', 'agent');
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/* ── Legacy git-mode registration migration ────────────────────────── */
|
|
196
|
+
// When legacy `pi install git:github.com/July24/pier` coexists with npm:pi-pier,
|
|
197
|
+
// both extensions register identical tools (todo_write / ask_user_question...), causing conflict at pi launch.
|
|
198
|
+
const LEGACY_GIT_SPEC = 'git:github.com/July24/pier';
|
|
199
|
+
|
|
200
|
+
function registeredPiPackages() {
|
|
201
|
+
try {
|
|
202
|
+
return JSON.parse(readFileSync(join(piAgentDir(), 'settings.json'), 'utf8'))?.packages ?? [];
|
|
203
|
+
} catch { return []; }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Clean up legacy git-mode registration before install/update; if --pi-spec explicitly specifies that git source, treat it as target and keep it. */
|
|
207
|
+
function removeLegacyGitRegistration() {
|
|
208
|
+
if (piSource() === LEGACY_GIT_SPEC) return;
|
|
209
|
+
if (!registeredPiPackages().includes(LEGACY_GIT_SPEC)) return;
|
|
210
|
+
if (tryRun('pi', ['remove', LEGACY_GIT_SPEC])) log(`✓ removed legacy git-mode registration (${LEGACY_GIT_SPEC}); using ${piSource()}`);
|
|
211
|
+
else { console.error('✗ failed to remove legacy git-mode registration'); log(` manual: pi remove ${LEGACY_GIT_SPEC}`); process.exitCode = 1; }
|
|
212
|
+
}
|
|
213
|
+
|
|
195
214
|
function npmNameFromSpec(spec) {
|
|
196
215
|
if (!spec.startsWith('npm:')) return null;
|
|
197
216
|
const rest = spec.slice(4);
|
|
@@ -265,7 +284,7 @@ function writeBootConfig(hmrDev, force) {
|
|
|
265
284
|
const target = dev ? DEV_BOOT_CONFIG : join(herdrConfigDir() ?? die('cannot resolve herdr plugin config-dir (is pier.workbench installed?)'), 'boot-config.json');
|
|
266
285
|
if (existsSync(target) && !force) {
|
|
267
286
|
let existing = null;
|
|
268
|
-
try { existing = JSON.parse(readFileSync(target, 'utf8')); } catch { /*
|
|
287
|
+
try { existing = JSON.parse(readFileSync(target, 'utf8')); } catch { /* treated as corrupted */ }
|
|
269
288
|
if (existing) {
|
|
270
289
|
log(`• ${target} exists, diff (existing → new):`);
|
|
271
290
|
for (const k of Object.keys(config)) {
|
|
@@ -320,7 +339,7 @@ function collectStatus() {
|
|
|
320
339
|
};
|
|
321
340
|
}
|
|
322
341
|
|
|
323
|
-
/* ──
|
|
342
|
+
/* ── Commands ─────────────────────────────────────────────────────── */
|
|
324
343
|
function usage() {
|
|
325
344
|
log(`pier-setup — install / update / inspect pier (pi-pier + pier.workbench)
|
|
326
345
|
|
|
@@ -365,6 +384,7 @@ function version() {
|
|
|
365
384
|
|
|
366
385
|
function update() {
|
|
367
386
|
checkEnv();
|
|
387
|
+
removeLegacyGitRegistration();
|
|
368
388
|
const me = readPkgVersion(ROOT);
|
|
369
389
|
const latestSetup = npmLatest('pier-setup');
|
|
370
390
|
if (latestSetup && me && cmpSemver(me, latestSetup) < 0) {
|
|
@@ -390,6 +410,7 @@ function install() {
|
|
|
390
410
|
const hmrDev = flags.has('--hmr-dev');
|
|
391
411
|
const force = flags.has('--force');
|
|
392
412
|
checkEnv();
|
|
413
|
+
removeLegacyGitRegistration();
|
|
393
414
|
|
|
394
415
|
if (dev) {
|
|
395
416
|
if (tryRun('pi', ['install', piSource()])) log('✓ pi extension installed (packages/pier-ext, local)');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pier-setup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "One-shot installer for pier: install / update / version / uninstall for pi-pier + pier.workbench",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"packages/*"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
|
-
"
|
|
34
|
+
"typecheck": "tsc --noEmit -p packages/pier-ext/tsconfig.json",
|
|
35
|
+
"test": "npm run typecheck && node --test \"packages/*/test/*.test.ts\"",
|
|
35
36
|
"test:mutation": "stryker run",
|
|
36
37
|
"prepare": "node ./install.mjs --prepare"
|
|
37
38
|
},
|
|
@@ -41,7 +42,8 @@
|
|
|
41
42
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
42
43
|
"@deepseek-ai/cordis-plugin-timer": "^1.1.3",
|
|
43
44
|
"@stryker-mutator/core": "^10.0.0",
|
|
44
|
-
"@stryker-mutator/typescript-checker": "^10.0.0"
|
|
45
|
+
"@stryker-mutator/typescript-checker": "^10.0.0",
|
|
46
|
+
"typescript": "~5.9.3"
|
|
45
47
|
},
|
|
46
48
|
"pi": {
|
|
47
49
|
"extensions": [
|