@groeponline/pi-wishcraft 0.27.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.0.1] - 2026-08-20
6
+
7
+ ## [1.0.0] - 2026-08-20
8
+
9
+ ### Added
10
+ - Wishcraft 1.0 cockpit: README lists `/skills doctor`, `/skills new`, `wishcraft.policy`, and idea-review.
11
+
5
12
  ## [0.27.2] - 2026-08-20
6
13
 
7
14
  ### Added
package/README.md CHANGED
@@ -34,11 +34,13 @@ Then restart pi or `/reload`. Peer range is `@earendil-works/pi-coding-agent` `>
34
34
  | --- | --- |
35
35
  | Status bar | Git, TPS (1s window over a 5s ring), context, cost, ports, queue. Default placement is the editor top border; `/powerline placement below` moves it. |
36
36
  | `alt+p` | Three overlays: Navigate, Configure, Status. In Navigate, `→` / `tab` opens per-segment detail (ports, git, cost, context). `alt+i` is the ports list. |
37
- | `# <idea>` | File-backed inbox. Does not send the prompt. `/ideas next` feeds the oldest active idea into the session. |
37
+ | `# <idea>` | File-backed inbox. Does not send the prompt. `/ideas` reviews status, tags, and skill insert. `/ideas next` feeds the oldest active idea into the session. |
38
38
  | `alt+s` | Stash the draft, ask something else, get it back when the run finishes. |
39
- | `/skills` | Substring search on name, description, and path. Enter inserts. |
39
+ | `/skills` | Overlay search on name, description, and path. Enter inserts. `/skills doctor` is the health table. `/skills new` writes a SKILL.md from a template. |
40
40
  | `!cmd` / bash mode | Managed shell with ghost suggestions from project history. No shell-native completion probes. |
41
41
  | Hooks + repairs | Command hooks on pi events. Custom-tool input repairs before execution. Kill-switch: `wishcraft.hooksEnabled`. |
42
+ | Read hints | Appends a one-line continuation hint after a partial `read`, so the model knows the next offset. Opt-out: `wishcraft.readHints: false`. |
43
+ | Policy | In-process deny/inject rules in global settings. No spawn. Kill-switch: `wishcraft.policyEnabled`. |
42
44
 
43
45
  Pi owns the footer chrome, feed scrolling, and input. Wishcraft supplies widgets, overlays, and the bash/stash/editor integrations. The bar is not clickable; actions are commands and overlays.
44
46
 
@@ -54,6 +56,9 @@ Activates on load. `/powerline` toggles it. `/powerline <preset>` switches look.
54
56
  /usage session / today / week from ~/.pi/agent/wishcraft-usage.json
55
57
  /repairs tool-input repair counters
56
58
  /skills skill manager
59
+ /skills doctor health table (broken frontmatter, dupes, unused, budget)
60
+ /skills new [name] write a SKILL.md from a template
61
+ /ideas idea review overlay (status, tags, skill insert)
57
62
  /wishcraft settings TUI
58
63
  /open-ports listening sockets
59
64
  /cd <path> continue this conversation in another directory
@@ -65,6 +70,7 @@ Queue:
65
70
 
66
71
  - `# <text>` current project; `# @global`, `# @current`, `# @alias`
67
72
  - `/idea`, `/ideas`, `/queue` for capture, send, retry, clear, archive
73
+ - `/ideas` overlay: `reviewStatus` (`idea` / `in-progress` / `done`), tags, Run with skill X
68
74
 
69
75
  Keybinds (`powerlineShortcuts`, applied after `/reload`; `null` disables):
70
76
 
package/ROADMAP.md CHANGED
@@ -125,13 +125,13 @@ extension from a feature-rich prototype.
125
125
  - **English operator UI** — overlays, skill manager, `/wishcraft` TUI
126
126
  (GRO-1422, #24, 0.27.1).
127
127
  - **`powerline.skills.count` + read hints + Status trim** (GRO-1420, 0.23.0).
128
+ - **1.0.0 cockpit cut** — README lists doctor, templates, policy, and
129
+ idea-review (GRO-1421). Semver leaves 0.x.
128
130
 
129
131
  ### Remaining open gaps (maturity)
130
- P0release gate for 1.0:
132
+ P1differentiation and quality:
131
133
  1. **Settings contract to pi core.** No `contributes.settings`/schema;
132
134
  users hand-edit JSON. *Fix: typed settings schema + contributions.*
133
-
134
- P1 — differentiation and quality:
135
135
  2. **Zero-config first run.** Install still expects JSON edits for the most
136
136
  useful features. *Fix: sensible defaults + first-run setup overlay.*
137
137
  3. **Perf budget / low-power mode.** Status renders every ~33ms; heavy
@@ -140,15 +140,12 @@ P1 — differentiation and quality:
140
140
  4. **Accessibility (no-color / reduced-motion).** Truecolor + animations
141
141
  (vibes, rainbow think) break on terminals without truecolor. *Fix:
142
142
  `NO_COLOR`/8-color + reduced-motion respect.*
143
- 5. **Semver policy / public API stability.** Still 0.x; top-15 extensions
144
- ship a stability contract + deprecation path. *Fix: documented API
145
- surface + semver policy. **1.0 cut: GRO-1421 (next).***
146
143
 
147
- P2 — full product, post-1.0 or ride-along on GRO-1415:
148
- 6. **Preset editor in-menu** — custom JSON-only today.
149
- 7. **Skill install from repo/npm** — discovery + doctor exist; install and
144
+ P2 — full product, post-1.0:
145
+ 5. **Preset editor in-menu** — custom JSON-only today.
146
+ 6. **Skill install from repo/npm** — discovery + doctor exist; install and
150
147
  curate missing.
151
- 8. **Host-status integration** — `ctx.ui.setStatus` beside the footer so
148
+ 7. **Host-status integration** — `ctx.ui.setStatus` beside the footer so
152
149
  status also shows in host UI (`skills.count` is a start; full coverage
153
150
  remains).
154
151
 
@@ -324,31 +321,26 @@ the same ring as the segment (no second source of truth).
324
321
 
325
322
  ## 1.0 — Cockpit
326
323
 
327
- Pas na 0.20. Geen parallelle 1.0-tak.
328
-
329
- 1. **`/skills doctor`** — kapot frontmatter, te lange descriptions
330
- (prompt-budget), duplicates global/project, ongebruikte skills.
331
- Tabel, geen essay.
332
- 2. **`/skills new` templates**standaard, browser-workflow,
333
- CLI-workflow, review-checklist. Install-van-GitHub/npm is
334
- post-1.0; 1.0 is doctor + templates, geen markt.
335
- 3. **Policy engine** hooks zonder spawn: `deny bash matching
336
- "sudo rm"`, `context inject when reading .env`. Command-hooks
337
- blijven voor alles wat niet in een regel past.
338
- 4. **Preset editor** links/rechts segmenten kiezen in het menu,
339
- opslaan in settings. Vandaag is custom JSON-only.
340
- 5. **Per-segment detail** (CHE-41): `→` in de navigator opent
341
- ports-lijst, git-samenvatting, cost-breakdown, context-math.
342
- Snapshot bij openen, geen live timer.
343
- 6. **Idee-review**`/ideas` in dezelfde overlay-taal; status
344
- idea / in-progress / done; tags; "verwerk met skill X".
345
- Welcome queue-widget: item oppakken prompt.
346
- 7. **ChefGroep-keys** stabiel `powerline.tps`, `powerline.ports`,
347
- `powerline.preset`, `powerline.skills.count` via
348
- `ctx.ui.setStatus`.
349
- 8. **Read-tool hints** — `tool_result` op `read` verrijken met
350
- "N regels, M–K getoond, volgende offset" alleen als core dat
351
- niet zelf al geeft. Eerst meten, dan aanvullen.
324
+ Shipped. The 0.x train ends at this cut. Preset editor and skill
325
+ install-from-repo stay post-1.0 (top-15 P2).
326
+
327
+ - [x] `/skills doctor` — broken frontmatter, long descriptions, global/project
328
+ duplicates, unused skills. Table, not an essay. (GRO-1416, #28)
329
+ - [x] `/skills new` templates — standard, browser-workflow, CLI-workflow,
330
+ review-checklist. No marketplace. (GRO-1417, #32)
331
+ - [x] Policy engine in-process deny/inject, no spawn. Command hooks remain
332
+ for anything that needs a process. (GRO-1418, #29)
333
+ - [x] Per-segment detail (CHE-41) `→` in Navigate, snapshot on open.
334
+ - [x] Idea review `/ideas` overlay: idea / in-progress / done, tags,
335
+ Run with skill X. Welcome widget shows next idea + `/ideas next`.
336
+ (GRO-1419, #31)
337
+ - [x] Status keys `powerline.tps`, `powerline.ports`, `powerline.preset`,
338
+ `powerline.skills.count` via `ctx.ui.setStatus`. Not the public pitch.
339
+ (GRO-1420)
340
+ - [x] Read-tool hints continuation hint when core omits a range/offset
341
+ summary. (GRO-1420)
342
+ - [x] English operator UI. (GRO-1422, #24)
343
+ - [x] README lists doctor, new, policy, and idea-review. `banner.png` only.
352
344
 
353
345
  ---
354
346
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groeponline/pi-wishcraft",
3
- "version": "0.27.2",
3
+ "version": "1.0.1",
4
4
  "description": "Wishcraft cockpit for the pi coding agent — powerline status, vibes, idea inbox, bash mode, and full customization.",
5
5
  "type": "module",
6
6
  "files": [
package/queue/store.ts CHANGED
@@ -432,6 +432,10 @@ export class PowerlineQueueStore {
432
432
  }
433
433
 
434
434
  if (archived.length > 0) {
435
+ // ponytail: commit the inbox (now without the archived items) before
436
+ // the archive. If the archive write fails, the items stay in the inbox
437
+ // and are re-archived next run instead of being duplicated.
438
+ this.writeItems(kept, now);
435
439
  mkdirSync(dirname(this.archivePath), { recursive: true });
436
440
  const existing = existsSync(this.archivePath)
437
441
  ? readFileSync(this.archivePath, "utf-8")
@@ -441,7 +445,6 @@ export class PowerlineQueueStore {
441
445
  this.archivePath,
442
446
  `${existing.trimEnd() ? `${existing.trimEnd()}\n` : ""}${lines}\n`,
443
447
  );
444
- this.writeItems(kept, now);
445
448
  }
446
449
 
447
450
  return { archived: archived.length, remainingSent };
@@ -112,18 +112,24 @@ export function runHookCommand(
112
112
  child.on("close", (code) => finish(code));
113
113
  // Hooks that exit before reading stdin (typical `exit 2` deny scripts)
114
114
  // close the pipe; ignore EPIPE so the harness still records the exit code.
115
- child.stdin?.on("error", (error: NodeJS.ErrnoException) => {
115
+ // One idempotent handler for the async stream event, the write callback,
116
+ // and the synchronous catch: EPIPE is expected (a hook that exits before
117
+ // reading stdin) and is ignored so its exit code still settles us; any
118
+ // other failure is recorded and settles the hook once.
119
+ const onStdinError = (error: NodeJS.ErrnoException): void => {
116
120
  if (error.code === "EPIPE") return;
117
- stderr += String(error);
118
- });
121
+ stderr += `wishcraft hook stdin error: ${error.message ?? String(error)}\n`;
122
+ finish(null);
123
+ };
119
124
 
125
+ child.stdin?.on("error", onStdinError);
120
126
  try {
121
- child.stdin?.write(JSON.stringify(payload));
127
+ child.stdin?.write(JSON.stringify(payload), (err) => {
128
+ if (err) onStdinError(err as NodeJS.ErrnoException);
129
+ });
122
130
  child.stdin?.end();
123
131
  } catch (error) {
124
- if ((error as NodeJS.ErrnoException).code !== "EPIPE") {
125
- stderr += String(error);
126
- }
132
+ onStdinError(error as NodeJS.ErrnoException);
127
133
  }
128
134
  });
129
135
  }
@@ -16,14 +16,13 @@ const CORE_SHOWING_LINES =
16
16
  /^\[Showing lines \d+[–-]\d+(?: of \d+)?\.(?: Use offset=\d+ to continue\.)?\]$/i;
17
17
  const CORE_MORE_LINES =
18
18
  /^\[?\d+ more lines in file\. Use offset=\d+ to continue\.?\]?$/i;
19
- const OWN_READ_HINT = /^\d+ lines, showing \d+[–-]\d+, next offset \d+$/;
19
+ const OWN_READ_HINT = /^\[wishcraft\] \d+ lines, showing \d+[–-]\d+, next offset \d+$/;
20
20
 
21
21
  function isCoreRangeFooter(line: string): boolean {
22
22
  const footer = line.trim();
23
23
  return (
24
24
  CORE_SHOWING_LINES.test(footer) ||
25
- CORE_MORE_LINES.test(footer) ||
26
- OWN_READ_HINT.test(footer)
25
+ CORE_MORE_LINES.test(footer)
27
26
  );
28
27
  }
29
28
 
@@ -33,6 +32,10 @@ function coreFooter(text: string): string {
33
32
  return lines[lines.length - 1]?.trim() ?? "";
34
33
  }
35
34
 
35
+ function hasOwnReadHint(text: string): boolean {
36
+ return OWN_READ_HINT.test(coreFooter(text));
37
+ }
38
+
36
39
  /** Default on. `wishcraft.readHints: false` is the opt-out. */
37
40
  export function readHintsEnabled(wishcraftSettings: unknown): boolean {
38
41
  if (
@@ -70,6 +73,7 @@ export function shouldAppendReadHint(
70
73
  if (!input) return false;
71
74
  if (input.offset === undefined && input.limit === undefined) return false;
72
75
  if (coreReadResultHasRangeSummary(text, details)) return false;
76
+ if (hasOwnReadHint(text)) return false;
73
77
 
74
78
  const lineCount = countContentLines(text);
75
79
  if (lineCount === 0) return false;
@@ -77,10 +81,15 @@ export function shouldAppendReadHint(
77
81
  const start = input.offset ?? 1;
78
82
  const end = start + lineCount - 1;
79
83
  const total = details?.truncation?.totalLines;
80
- if (total !== undefined && end >= total) return false;
81
- if (input.limit !== undefined && lineCount < input.limit) return false;
82
-
83
- return true;
84
+ if (total !== undefined) {
85
+ // With a known file length we can tell whether the window reached EOF.
86
+ return end < total;
87
+ }
88
+ // Without a total we can only infer more content when the window filled the
89
+ // requested limit exactly; otherwise the read likely reached EOF and we must
90
+ // not point the model past the end of the file.
91
+ if (input.limit === undefined) return false;
92
+ return lineCount >= input.limit;
84
93
  }
85
94
 
86
95
  /** Format the English continuation hint for a partial read window. */
@@ -95,9 +104,9 @@ export function formatReadHint(
95
104
  const next = end + 1;
96
105
  const total = details?.truncation?.totalLines;
97
106
  if (total !== undefined) {
98
- return `${total} lines, showing ${start}–${end}, next offset ${next}`;
107
+ return `[wishcraft] ${total} lines, showing ${start}–${end}, next offset ${next}`;
99
108
  }
100
- return `${lineCount} lines, showing ${start}–${end}, next offset ${next}`;
109
+ return `[wishcraft] ${lineCount} lines, showing ${start}–${end}, next offset ${next}`;
101
110
  }
102
111
 
103
112
  export function extractReadToolResultText(content: unknown): string {
@@ -70,7 +70,7 @@ import {
70
70
  import { CONTEXT_STATUS_RENDER_MS } from "../core/constants.ts";
71
71
  import type { RuntimeState } from "../core/types.ts";
72
72
  import { isStaleExtensionContextError } from "./stale-context.ts";
73
- import { dismissWelcome, scheduleDismissWelcome } from "../welcome/welcome-control.ts";
73
+ import { dismissWelcome } from "../welcome/welcome-control.ts";
74
74
  import {
75
75
  bindSkillsCountPublisher,
76
76
  clearSkillsCountPublisher,
@@ -35,6 +35,8 @@ export interface ConfigItem {
35
35
  hint?: string;
36
36
  /** Detail explanation (optional). */
37
37
  description?: string;
38
+ /** Effective value when the setting is absent (toggles only; defaults to false). */
39
+ default?: boolean;
38
40
  }
39
41
 
40
42
  export interface ConfigGroup {
@@ -166,7 +168,7 @@ export function buildConfigGroups(settings: Record<string, unknown>): ConfigGrou
166
168
  title: "Skills",
167
169
  items: [
168
170
  { label: "Inline expand /command and $skill", path: "wishcraft.inlineSkills", kind: "toggle", hint: "needs a restart to take effect" },
169
- { label: "Read hints", path: "wishcraft.readHints", kind: "toggle", hint: "off = no continuation hint after partial reads" },
171
+ { label: "Read hints", path: "wishcraft.readHints", kind: "toggle", default: true, hint: "off = no continuation hint after partial reads" },
170
172
  ],
171
173
  },
172
174
  {
@@ -200,12 +202,24 @@ function isPrintable(data: string): boolean {
200
202
  return data.length === 1 && data >= " " && data <= "~";
201
203
  }
202
204
 
203
- function displayValue(item: ConfigItem, value: ConfigValue): string {
205
+ /** Value shown for a config item given its stored value. */
206
+ export function displayValue(item: ConfigItem, value: ConfigValue): string {
207
+ // An unset toggle renders as its declared default (read hints default on).
208
+ if (item.kind === "toggle" && (value === null || value === undefined)) {
209
+ return (item.default ?? false) ? "on" : "off";
210
+ }
204
211
  if (value === null || value === undefined || value === "") return item.kind === "toggle" ? "off" : "—";
205
212
  if (item.kind === "toggle") return value ? "on" : "off";
206
213
  return String(value);
207
214
  }
208
215
 
216
+ /** Next stored boolean after toggling `item` from its current `value`. */
217
+ export function nextToggleValue(item: ConfigItem, value: ConfigValue): boolean {
218
+ const effective =
219
+ item.kind === "toggle" && value === null ? (item.default ?? false) : value === true;
220
+ return !effective;
221
+ }
222
+
209
223
  function coerce(item: ConfigItem, current: ConfigValue, next: string): ConfigValue {
210
224
  if (item.kind === "number") {
211
225
  const n = Number.parseInt(next, 10);
@@ -288,7 +302,7 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
288
302
 
289
303
  const toggle = (item: ConfigItem) => {
290
304
  const cur = readConfigPath(settings, item.path);
291
- const ok = writeConfigPath(cwd, item.path, !(cur === true));
305
+ const ok = writeConfigPath(cwd, item.path, nextToggleValue(item, cur));
292
306
  settings = readSettings(cwd);
293
307
  groups = buildConfigGroups(settings);
294
308
  ctx.ui.notify(