@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 +7 -0
- package/README.md +8 -2
- package/ROADMAP.md +27 -35
- package/package.json +1 -1
- package/queue/store.ts +4 -1
- package/src/extension/hooks/hooks-runner.ts +13 -7
- package/src/extension/session/read-hints.ts +18 -9
- package/src/extension/session/session-lifecycle.ts +1 -1
- package/src/extension/settings/wishcraft-config.ts +17 -3
package/CHANGELOG.md
CHANGED
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` |
|
|
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
|
-
|
|
132
|
+
P1 — differentiation 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
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
107
|
+
return `[wishcraft] ${total} lines, showing ${start}–${end}, next offset ${next}`;
|
|
99
108
|
}
|
|
100
|
-
return
|
|
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
|
|
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
|
-
|
|
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,
|
|
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(
|