@groeponline/pi-wishcraft 0.27.0 → 0.27.2

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/AGENTS.md CHANGED
@@ -57,6 +57,8 @@ user customization.
57
57
  - **Bun/Node-native TS:** `import` paths carry `.ts` extensions
58
58
  (`allowImportingTsExtensions`), `node:`-prefixed builtins, `node --test`
59
59
  with type stripping. No build step.
60
+ - **English UI:** operator overlays, notify strings, and `/wishcraft` copy are
61
+ English. Do not add Dutch UI strings.
60
62
  - **Tests:** behavior changes in `src/` need a focused regression test near
61
63
  the existing tests for that subsystem; structural tests assert on module
62
64
  files in `src/` (never `index.ts`).
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.27.2] - 2026-08-20
6
+
7
+ ### Added
8
+ - `examples/settings.example.json` — every setting at its default, including
9
+ `wishcraft.policy`, `readHints`, and other post-0.22 keys.
10
+ - ROADMAP top-15 maturity track rebaselined on 0.27.x (English); landed
11
+ 0.23–0.27 items moved to "Already shipped".
12
+
13
+ ## [0.27.1] - 2026-08-20
14
+
15
+ ### Changed
16
+ - Operator UI is English: skill manager, `/wishcraft` TUI, overlay empty-match row, and skill-health warnings.
17
+
5
18
  ## [0.27.0] - 2026-08-20
6
19
 
7
20
  ### Added
package/README.md CHANGED
@@ -91,7 +91,7 @@ Keybinds (`powerlineShortcuts`, applied after `/reload`; `null` disables):
91
91
  }
92
92
  ```
93
93
 
94
- `chef` is muted colors, slash separators, live TPS in/out, and TCP port count. Built-in presets: `default`, `minimal`, `compact`, `full`, `nerd`, `ascii`, `chef`. Custom segments, labels, layout, and presets are documented in [docs/configuration.md](docs/configuration.md).
94
+ `chef` is muted colors, slash separators, live TPS in/out, and TCP port count. Built-in presets: `default`, `minimal`, `compact`, `full`, `nerd`, `ascii`, `chef`. Custom segments, labels, layout, and presets are documented in [docs/configuration.md](docs/configuration.md). For every setting at its default, see [`examples/settings.example.json`](examples/settings.example.json).
95
95
 
96
96
  Nerd Fonts auto-detect for iTerm, WezTerm, Kitty, Ghostty, and Alacritty; ASCII otherwise. `POWERLINE_NERD_FONTS=0` forces ASCII.
97
97
 
package/ROADMAP.md CHANGED
@@ -98,6 +98,63 @@ Niet de fork. Niet de SaaS-agent.
98
98
  documentatie die waar is. Done = README dekt alles wat we shipten,
99
99
  geen kapotte footer-belofte.
100
100
 
101
+ ## Top-15 track: remaining maturity gaps
102
+
103
+ Written 2026-08-20, rebaselined on 0.27.1 (code review, not this ROADMAP
104
+ alone). Feature density is high; several original gaps closed in
105
+ 0.23–0.27. What remains is the maturity layer that separates a top-15 pi
106
+ extension from a feature-rich prototype.
107
+
108
+ ### Already shipped (0.22.x–0.27.x)
109
+ - **CHE-41 per-segment detail** — `→` in Navigate, snapshot on open (0.22.1).
110
+ - **CHE-42 drill-down** — #19 + Configure in #13.
111
+ - **Changelog roll** — version headers drive the what's-new panel.
112
+ - **Per-segment fault isolation** — throwing custom segments show `!id`
113
+ instead of blanking the footer (#34, 0.23.1).
114
+ - **macOS open_ports** — netstat dot-address parsing (#34, 0.23.1).
115
+ - **GitHub Release per npm tag** (#25, 0.23.2).
116
+ - **CodeQL proto-pollution hardening** (#27, 0.23.3).
117
+ - **setupHooks wired** (#26, 0.23.4).
118
+ - **Policy engine** — declarative deny/inject, no spawn (GRO-1418, #29,
119
+ 0.24.0).
120
+ - **`/skills doctor`** — broken frontmatter, dupes, unused, budget
121
+ (GRO-1416, #28, 0.24.0 / 0.25.0 tag).
122
+ - **`/skills new` templates** — no marketplace (GRO-1417, #32, 0.26.0).
123
+ - **`/ideas` review overlay** — status, tags, skill insert (GRO-1419, #31,
124
+ 0.27.0).
125
+ - **English operator UI** — overlays, skill manager, `/wishcraft` TUI
126
+ (GRO-1422, #24, 0.27.1).
127
+ - **`powerline.skills.count` + read hints + Status trim** (GRO-1420, 0.23.0).
128
+
129
+ ### Remaining open gaps (maturity)
130
+ P0 — release gate for 1.0:
131
+ 1. **Settings contract to pi core.** No `contributes.settings`/schema;
132
+ users hand-edit JSON. *Fix: typed settings schema + contributions.*
133
+
134
+ P1 — differentiation and quality:
135
+ 2. **Zero-config first run.** Install still expects JSON edits for the most
136
+ useful features. *Fix: sensible defaults + first-run setup overlay.*
137
+ 3. **Perf budget / low-power mode.** Status renders every ~33ms; heavy
138
+ segments (bash-history, git) can hit the hot path. *Fix: configurable
139
+ refresh + lite mode.*
140
+ 4. **Accessibility (no-color / reduced-motion).** Truecolor + animations
141
+ (vibes, rainbow think) break on terminals without truecolor. *Fix:
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
+
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
150
+ curate missing.
151
+ 8. **Host-status integration** — `ctx.ui.setStatus` beside the footer so
152
+ status also shows in host UI (`skills.count` is a start; full coverage
153
+ remains).
154
+
155
+ Linear tickets for open gaps are still to be filed (not invented in this
156
+ ROADMAP).
157
+
101
158
  ---
102
159
 
103
160
  ## 0.19.0 — Correctheid
@@ -219,50 +276,49 @@ de zes gevallen + de parse-vóór-wrap invariant.
219
276
 
220
277
  ### PR G — overlay-submenus (CHE-42) — ✅ #19 + expansion (#13 reopen)
221
278
 
222
- Het `alt+p`-menu krijgt gestapelde `SelectList`-overlays (pijltjes +
223
- descriptions) in plaats van platte `ctx.ui.select`. Max drie
224
- top-level ingangen. #19 landt de top-level boom (Navigate / Configure /
225
- Status). Configure gebruikt nu `showSelectOverlay` (CHE-42 rest). CHE-40
226
- is Done (#18). CHE-41 is Done: `→` in Navigate, snapshot bij openen, geen tweede `alt+i`-pad.
279
+ The `alt+p` menu gets stacked `SelectList` overlays (arrows +
280
+ descriptions) instead of flat `ctx.ui.select`. Maximum three top-level
281
+ entries. #19 lands the top-level tree (Navigate / Configure /
282
+ Status). Configure now uses `showSelectOverlay` (CHE-42 remainder). CHE-40
283
+ is Done (#18). CHE-41 is Done: `→` in Navigate, snapshot on open, no second `alt+i` path.
227
284
 
228
285
  ### PR H — skills manager v2 UI + token-overlays
229
286
 
230
- Vervangt `skill-manager.ts` (242 regels). Data-laag bouwt op Pi
287
+ Replaces `skill-manager.ts` (242 lines). The data layer builds on Pi
231
288
  core `loadSkills` / `loadSkillsFromDir` / `Skill` /
232
- `SkillFrontmatter` (publiek geëxporteerd in
289
+ `SkillFrontmatter` (publicly exported in
233
290
  `@earendil-works/pi-coding-agent`).
234
291
 
235
- UI, niet onderhandelbaar:
236
-
237
- - Zoeken die werkt: substring op naam + beschrijving + pad,
238
- case-insensitive; `ctrl+u` wist; lege match = rij
239
- `geen skills voor '<q>'`.
240
- - Categorieën met kopregels (bundled / global / project / prompts);
241
- `tab` wisselt filter; `s` sorteert naamgebruik.
242
- - `→` detail (frontmatter-tabel, usage, pad, body + scroll);
243
- `enter` insert; `e` opent `$EDITOR` (default nvim) via extern
244
- spawn zoals pi's `!`; `n` nieuwe skill in
245
- `~/.pi/agent/skills/<naam>/SKILL.md`; `d` delete + confirm.
246
- - Usage-ledger `~/.pi/agent/skill-usage.json` (naam, timestamp,
247
- trigger-type). Best-effort, nooit blokkerend op de input hot path.
248
- - Skill health: core-diagnostics als waarschuwingsicoon; `?` legt
249
- het uit.
250
-
251
- Zelfde chrome als de segment-navigator. Nederlands, direct.
252
-
253
- Tegelijk, klein:
254
-
255
- - `/tps` overlay: in/out, piek + gemiddelde over de bestaande ring.
256
- Geen nieuwe sampler.
257
- - `/usage` overlay: vandaag / deze week / deze sessie; per model;
258
- cache-hit %; ASCII-sparkline. File
259
- `~/.pi/agent/wishcraft-usage.json` (append-only, compaction bij
260
- drempel).
261
- - `wishcraft.tokenBudget.daily` kleurt het segment rood en waarschuwt
262
- in welcome bij 80% / 100%. Nooit blokkerend.
263
-
264
- Done: filter-tests op substring; usage-ledger tests; `/tps` leest
265
- dezelfde ring als het segment (geen tweede waarheid).
292
+ UI, non-negotiable:
293
+
294
+ - Working search: substring on name + description + path,
295
+ case-insensitive; `ctrl+u` clears; empty match = row
296
+ `no skills for '<q>'`.
297
+ - Categories with headings (bundled / global / project / prompts);
298
+ `tab` switches the filter; `s` sorts nameusage.
299
+ - `→` detail (frontmatter table, usage, path, body + scroll);
300
+ `enter` inserts; `e` opens `$EDITOR` (default nvim) via external
301
+ spawn like pi's `!`; `n` creates a new skill in
302
+ `~/.pi/agent/skills/<name>/SKILL.md`; `d` delete + confirm.
303
+ - Usage-ledger `~/.pi/agent/skill-usage.json` (name, timestamp,
304
+ trigger type). Best-effort, never blocking on the input hot path.
305
+ - Skill health: core-diagnostics as a warning icon; `?` explains it.
306
+
307
+ Same chrome as the segment navigator. English, direct.
308
+
309
+ At the same time, small additions:
310
+
311
+ - `/tps` overlay: in/out, peak + average over the existing ring.
312
+ No new sampler.
313
+ - `/usage` overlay: today / this week / this session; per model;
314
+ cache-hit %; ASCII sparkline. File
315
+ `~/.pi/agent/wishcraft-usage.json` (append-only, compaction at
316
+ threshold).
317
+ - `wishcraft.tokenBudget.daily` colors the segment red and warns
318
+ in the welcome at 80% / 100%. Never blocking.
319
+
320
+ Done: substring filter tests; usage-ledger tests; `/tps` reads
321
+ the same ring as the segment (no second source of truth).
266
322
 
267
323
  ---
268
324
 
@@ -314,7 +370,9 @@ die package nog leeft.
314
370
 
315
371
  ## Kwaliteit (altijd)
316
372
 
317
- - Overlay-logica testbaar via pure functies. Geen headless `ctx.ui`.
373
+ - Overlay logic is testable via pure functions. No headless `ctx.ui`.
374
+ - **English UI:** operator overlays, notify strings, and `/wishcraft` copy are
375
+ English. Do not add Dutch UI strings.
318
376
  - `prepublishOnly` = `tsc --noEmit`. Een slecht type ship't niet.
319
377
  - `madge --circular` blijft CI. Nieuwe map in `src/` → check mee.
320
378
  - Dependabot-vulns (devDep-transitief): waiven, track op GRO-603.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groeponline/pi-wishcraft",
3
- "version": "0.27.0",
3
+ "version": "0.27.2",
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": [
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * wishcraft-config.ts
3
3
  * ---------------------------------------------------------------------------
4
- * `/wishcraft` — één configuratie-TUI voor álle wishcraft-instellingen:
5
- * overzichtelijk per groep én direct aanpasbaar (toggle, keuze, tekst,
6
- * getal), live weggeschreven naar settings en direct zichtbaar.
7
- * Data-driven: instellingen worden gedeclareerd als ConfigItem[]; de overlay
8
- * rendert en bewerkt ze generiek.
4
+ * `/wishcraft` — one configuration TUI for every wishcraft setting:
5
+ * grouped, directly editable (toggle, choice, text, number), written live
6
+ * to settings and visible immediately. Data-driven: settings are declared
7
+ * as ConfigItem[]; the overlay renders and edits them generically.
9
8
  * ---------------------------------------------------------------------------
10
9
  */
11
10
 
@@ -24,17 +23,17 @@ import { parsePowerlineConfig } from "../../config/powerline-config.ts";
24
23
  export type ConfigValue = boolean | string | number | null;
25
24
 
26
25
  export interface ConfigItem {
27
- /** Label in de lijst. */
26
+ /** Label in the list. */
28
27
  label: string;
29
- /** Pad binnen settings ("powerline.placement", "wishcraft.hooksEnabled", ...). */
28
+ /** Path inside settings ("powerline.placement", "wishcraft.hooksEnabled", ...). */
30
29
  path: string;
31
- /** Bewerkingsoptype. */
30
+ /** Edit kind. */
32
31
  kind: "toggle" | "select" | "text" | "number";
33
- /** Voor select: de keuzes. */
32
+ /** For select: the choices. */
34
33
  choices?: string[];
35
- /** Hulpregels onder de groep. */
34
+ /** Hint under the group. */
36
35
  hint?: string;
37
- /** Uitleg bij het item in detail (optioneel). */
36
+ /** Detail explanation (optional). */
38
37
  description?: string;
39
38
  }
40
39
 
@@ -43,7 +42,7 @@ export interface ConfigGroup {
43
42
  items: ConfigItem[];
44
43
  }
45
44
 
46
- /** Genest uitlezen: "wishcraft.hooksEnabled" → settings.wishcraft.hooksEnabled. */
45
+ /** Nested read: "wishcraft.hooksEnabled" → settings.wishcraft.hooksEnabled. */
47
46
  export function readConfigPath(settings: Record<string, unknown>, path: string): ConfigValue {
48
47
  let cur: unknown = settings;
49
48
  for (const part of path.split(".")) {
@@ -130,61 +129,61 @@ const SEPARATORS = [
130
129
  "star",
131
130
  ];
132
131
 
133
- /** Bouw de groepen op basis van huidige settings (waarden live getoond). */
132
+ /** Build groups from current settings (values shown live). */
134
133
  export function buildConfigGroups(settings: Record<string, unknown>): ConfigGroup[] {
135
134
  return [
136
135
  {
137
- title: "Statusbalk",
136
+ title: "Status bar",
138
137
  items: [
139
138
  { label: "Preset", path: "powerline.preset", kind: "select", choices: ["default", "minimal", "compact", "full", "nerd", "ascii", "chef"] },
140
139
  { label: "Separator", path: "powerline.separator", kind: "select", choices: SEPARATORS },
141
- { label: "Plaatsing", path: "powerline.placement", kind: "select", choices: ["above", "below"] },
142
- { label: "Pad-modus", path: "powerline.segmentOptions.path.mode", kind: "select", choices: ["basename", "abbreviated", "full"] },
143
- { label: "Pad-maxlengte", path: "powerline.segmentOptions.path.maxLength", kind: "number", hint: "0 = onbeperkt" },
144
- { label: "Tijd formaat", path: "powerline.segmentOptions.time.format", kind: "select", choices: ["12h", "24h"] },
145
- { label: "Tijd seconden", path: "powerline.segmentOptions.time.showSeconds", kind: "toggle" },
146
- { label: "Git host-iconen", path: "powerline.segmentOptions.git.hostIcon", kind: "toggle" },
140
+ { label: "Placement", path: "powerline.placement", kind: "select", choices: ["above", "below"] },
141
+ { label: "Path mode", path: "powerline.segmentOptions.path.mode", kind: "select", choices: ["basename", "abbreviated", "full"] },
142
+ { label: "Path max length", path: "powerline.segmentOptions.path.maxLength", kind: "number", hint: "0 = unlimited" },
143
+ { label: "Time format", path: "powerline.segmentOptions.time.format", kind: "select", choices: ["12h", "24h"] },
144
+ { label: "Time seconds", path: "powerline.segmentOptions.time.showSeconds", kind: "toggle" },
145
+ { label: "Git host icons", path: "powerline.segmentOptions.git.hostIcon", kind: "toggle" },
147
146
  { label: "Git ahead/behind", path: "powerline.segmentOptions.git.showAheadBehind", kind: "toggle" },
148
- { label: "Git laatste commit", path: "powerline.segmentOptions.git.showCommit", kind: "toggle" },
149
- { label: "Context formaat", path: "powerline.segmentOptions.context.format", kind: "select", choices: ["full", "percent"] },
150
- { label: "Cache-read formaat", path: "powerline.segmentOptions.cache_read.format", kind: "select", choices: ["tokens", "percent", "both"] },
151
- { label: "Kosten toning", path: "powerline.segmentOptions.cost.subscriptionDisplay", kind: "select", choices: ["subscription", "reported-cost", "both"] },
152
- { label: "Valuta", path: "powerline.segmentOptions.cost.currency", kind: "text" },
153
- { label: "Ports incl. UDP", path: "powerline.segmentOptions.openPorts.includeUdp", kind: "toggle" },
154
- { label: "TPS venster (ms)", path: "powerline.segmentOptions.tps.windowMs", kind: "number", hint: "default 1000" },
155
- { label: "TPS modus", path: "powerline.segmentOptions.tps.mode", kind: "select", choices: ["both", "out", "in"] },
156
- { label: "TPS label", path: "powerline.segmentLabels.tps", kind: "text", hint: "leeg = geen label" },
147
+ { label: "Git latest commit", path: "powerline.segmentOptions.git.showCommit", kind: "toggle" },
148
+ { label: "Context format", path: "powerline.segmentOptions.context.format", kind: "select", choices: ["full", "percent"] },
149
+ { label: "Cache-read format", path: "powerline.segmentOptions.cache_read.format", kind: "select", choices: ["tokens", "percent", "both"] },
150
+ { label: "Cost display", path: "powerline.segmentOptions.cost.subscriptionDisplay", kind: "select", choices: ["subscription", "reported-cost", "both"] },
151
+ { label: "Currency", path: "powerline.segmentOptions.cost.currency", kind: "text" },
152
+ { label: "Ports include UDP", path: "powerline.segmentOptions.openPorts.includeUdp", kind: "toggle" },
153
+ { label: "TPS window (ms)", path: "powerline.segmentOptions.tps.windowMs", kind: "number", hint: "default 1000" },
154
+ { label: "TPS mode", path: "powerline.segmentOptions.tps.mode", kind: "select", choices: ["both", "out", "in"] },
155
+ { label: "TPS label", path: "powerline.segmentLabels.tps", kind: "text", hint: "empty = no label" },
157
156
  ],
158
157
  },
159
158
  {
160
- title: "Welkom & vibes",
159
+ title: "Welcome & vibes",
161
160
  items: [
162
- { label: "Welcome overlay", path: "powerline.welcome", kind: "toggle", hint: "aan = overlay bij opstart, uit = geen welcome" },
163
- { label: "Wishcraft-ballon animeren", path: "wishcraft.welcome.animateLantern", kind: "toggle", hint: "flikker-effect op de wensballon" },
161
+ { label: "Welcome overlay", path: "powerline.welcome", kind: "toggle", hint: "on = overlay at startup, off = no welcome" },
162
+ { label: "Animate wishcraft lantern", path: "wishcraft.welcome.animateLantern", kind: "toggle", hint: "flicker on the lantern" },
164
163
  ],
165
164
  },
166
165
  {
167
166
  title: "Skills",
168
167
  items: [
169
- { label: "Inline-expansie /command en $skill", path: "wishcraft.inlineSkills", kind: "toggle", hint: "nog niet actief zonder herstart" },
168
+ { label: "Inline expand /command and $skill", path: "wishcraft.inlineSkills", kind: "toggle", hint: "needs a restart to take effect" },
170
169
  { label: "Read hints", path: "wishcraft.readHints", kind: "toggle", hint: "off = no continuation hint after partial reads" },
171
170
  ],
172
171
  },
173
172
  {
174
- title: "Hooks & repairs (harness-laag)",
173
+ title: "Hooks & repairs (harness)",
175
174
  items: [
176
- { label: "Hooks ingeschakeld", path: "wishcraft.hooksEnabled", kind: "toggle", hint: "commandcode-achtige preToolUse/postToolUse/sessionStart hooks" },
175
+ { label: "Hooks enabled", path: "wishcraft.hooksEnabled", kind: "toggle", hint: "preToolUse / postToolUse / sessionStart command hooks" },
177
176
  { label: "Tool-input repairs", path: "wishcraft.repairsEnabled", kind: "toggle", hint: "null-for-optional, auto-link, json-array, path aliases" },
178
- { label: "Dagelijks tokenbudget", path: "wishcraft.tokenBudget.daily", kind: "number", hint: "kleurt cost-segment; blokkeert nooit. 0 = uit" },
177
+ { label: "Daily token budget", path: "wishcraft.tokenBudget.daily", kind: "number", hint: "colours the cost segment; never blocks. 0 = off" },
179
178
  ],
180
179
  },
181
180
  {
182
- title: "Sneltoetsen",
181
+ title: "Shortcuts",
183
182
  items: [
184
- { label: "Menu", path: "powerlineShortcuts.menu", kind: "text", hint: "bv. alt+p" },
183
+ { label: "Menu", path: "powerlineShortcuts.menu", kind: "text", hint: "e.g. alt+p" },
185
184
  { label: "Info", path: "powerlineShortcuts.info", kind: "text" },
186
185
  { label: "Stash", path: "powerlineShortcuts.stashHistory", kind: "text" },
187
- { label: "Idee", path: "powerlineShortcuts.ideaCapture", kind: "text" },
186
+ { label: "Idea", path: "powerlineShortcuts.ideaCapture", kind: "text" },
188
187
  { label: "Queue", path: "powerlineShortcuts.queueOpen", kind: "text" },
189
188
  ],
190
189
  },
@@ -202,8 +201,8 @@ function isPrintable(data: string): boolean {
202
201
  }
203
202
 
204
203
  function displayValue(item: ConfigItem, value: ConfigValue): string {
205
- if (value === null || value === undefined || value === "") return item.kind === "toggle" ? "uit" : "—";
206
- if (item.kind === "toggle") return value ? "aan" : "uit";
204
+ if (value === null || value === undefined || value === "") return item.kind === "toggle" ? "off" : "—";
205
+ if (item.kind === "toggle") return value ? "on" : "off";
207
206
  return String(value);
208
207
  }
209
208
 
@@ -220,7 +219,7 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
220
219
  let settings = readSettings(cwd);
221
220
  let groups = buildConfigGroups(settings);
222
221
 
223
- // platte rijenlijst: groepstitels + items
222
+ // flat row list: group titles + items
224
223
  type Row =
225
224
  | { type: "group"; title: string }
226
225
  | { type: "item"; group: number; item: ConfigItem };
@@ -239,7 +238,7 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
239
238
  const wrapRow = (t: string, w: number) =>
240
239
  `${border("│")}${truncateToWidth(t, w, "…", true)}${border("│")}`;
241
240
 
242
- let selected = 1; // eerste item (rij 0 is een groepstitel)
241
+ let selected = 1; // first item (row 0 is a group title)
243
242
  let editing = false;
244
243
  let editBuffer = "";
245
244
 
@@ -254,12 +253,12 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
254
253
  if (!cur) return;
255
254
  const { item } = cur;
256
255
  let value: ConfigValue;
257
- if (item.kind === "toggle") value = next === "aan";
256
+ if (item.kind === "toggle") value = next === "on";
258
257
  else value = coerce(item, readConfigPath(settings, item.path), next);
259
258
  const ok = writeConfigPath(cwd, item.path, value);
260
259
  settings = readSettings(cwd);
261
260
  groups = buildConfigGroups(settings);
262
- // live herladen powerline-config + statusbalk
261
+ // live-reload powerline config + status bar
263
262
  if (item.path.startsWith("powerline")) {
264
263
  setConfig({
265
264
  ...stateConfig,
@@ -268,7 +267,7 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
268
267
  rt.tuiRef?.requestRender?.();
269
268
  }
270
269
  ctx.ui.notify(
271
- ok ? `${item.label}: ${displayValue(item, value)} (opgeslagen)` : `${item.label} niet opgeslagen (settings.json?)`,
270
+ ok ? `${item.label}: ${displayValue(item, value)} (saved)` : `${item.label} not saved (settings.json?)`,
272
271
  ok ? "info" : "warning",
273
272
  );
274
273
  };
@@ -282,7 +281,7 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
282
281
  settings = readSettings(cwd);
283
282
  groups = buildConfigGroups(settings);
284
283
  ctx.ui.notify(
285
- ok ? `${item.label}: ${next} (opgeslagen)` : `${item.label} niet opgeslagen`,
284
+ ok ? `${item.label}: ${next} (saved)` : `${item.label} not saved`,
286
285
  ok ? "info" : "warning",
287
286
  );
288
287
  };
@@ -293,7 +292,7 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
293
292
  settings = readSettings(cwd);
294
293
  groups = buildConfigGroups(settings);
295
294
  ctx.ui.notify(
296
- ok ? `${item.label}: ${!(cur === true) ? "aan" : "uit"} (opgeslagen)` : `${item.label} niet opgeslagen`,
295
+ ok ? `${item.label}: ${!(cur === true) ? "on" : "off"} (saved)` : `${item.label} not saved`,
297
296
  ok ? "info" : "warning",
298
297
  );
299
298
  };
@@ -304,12 +303,12 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
304
303
  const lines: string[] = [];
305
304
  lines.push(border(`╭${"─".repeat(innerWidth)}╮`));
306
305
  lines.push(
307
- wrapRow(theme.fg("accent", theme.bold("Wishcraft · configuratie")), innerWidth),
306
+ wrapRow(theme.fg("accent", theme.bold("Wishcraft · configuration")), innerWidth),
308
307
  );
309
308
  lines.push(border(`├${"─".repeat(innerWidth)}┤`));
310
309
 
311
310
  const rows = buildRows();
312
- // scroll-venster rondom selectie
311
+ // scroll window around the selection
313
312
  let start = Math.max(0, selected - Math.floor(LIST_ROWS / 2));
314
313
  let end = Math.min(start + LIST_ROWS, rows.length);
315
314
  if (end - start < Math.min(LIST_ROWS, rows.length)) start = Math.max(0, end - LIST_ROWS);
@@ -341,8 +340,8 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
341
340
  theme.fg(
342
341
  "dim",
343
342
  editing
344
- ? "typ=waarde · enter=opslaan · esc=annuleren"
345
- : "↑↓ · enter=kiezen/bewerken (←→ wisselt) · esc=sluiten",
343
+ ? "type=value · enter=save · esc=cancel"
344
+ : "↑↓ · enter=select/edit (←→ cycles) · esc=close",
346
345
  ),
347
346
  innerWidth,
348
347
  ),
@@ -388,7 +387,7 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
388
387
  } while (rows[selected]!.type === "group");
389
388
  } else if (matchesKey(data, "pageUp")) {
390
389
  selected = Math.max(1, selected - LIST_ROWS);
391
- // niet op een groepskop landen: schuif door naar het volgende item
390
+ // do not land on a group header: skip to the next item
392
391
  while (selected < rows.length - 1 && rows[selected]!.type === "group")
393
392
  selected++;
394
393
  } else if (matchesKey(data, "pageDown")) {
@@ -424,10 +423,10 @@ export async function showWishcraftConfig(rt: RuntimeState, ctx: any): Promise<v
424
423
  );
425
424
  }
426
425
 
427
- /** Registreer /wishcraft. */
426
+ /** Register /wishcraft. */
428
427
  export function registerWishcraftConfigCommand(pi: ExtensionAPI, rt: RuntimeState): void {
429
428
  pi.registerCommand("wishcraft", {
430
- description: "Configureer alles van wishcraft in één overzicht (statusbalk, welcome, hooks, sneltoetsen)",
429
+ description: "Configure all wishcraft settings in one overlay (status bar, welcome, hooks, shortcuts)",
431
430
  handler: async (_args: string, ctx: any) => {
432
431
  if (!rt.enabled || !ctx.hasUI) {
433
432
  ctx.ui.notify("Powerline UI is disabled", "info");
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * inline-invocation.ts
3
3
  * ------------------------------------------------------------------------
4
- * Inline trigger expansie zoals Cursor (met /) en Codex (met $).
4
+ * Inline trigger expansion like Cursor (`/`) and Codex (`$`).
5
5
  *
6
- * /command -> vervangt door inhoud van command.md skill file
7
- * $skill -> vervangt door inhoud van skill.md skill file
6
+ * /command -> replaced with the command.md skill body
7
+ * $skill -> replaced with the skill.md skill body
8
8
  *
9
- * Discovery en cache leven in skill-registry.ts (pi core loadSkills + TTL),
10
- * zodat nieuwe skills zonder herstart werken en usage bijgehouden wordt.
9
+ * Discovery and cache live in skill-registry.ts (pi core loadSkills + TTL)
10
+ * so new skills work without a restart and usage is tracked.
11
11
  * ------------------------------------------------------------------------
12
12
  */
13
13
 
@@ -64,7 +64,7 @@ function isExcluded(index: number, ranges: Array<[number, number]>): boolean {
64
64
  export function expandInlineTriggers(text: string): string {
65
65
  const availableSkills = getAvailableSkills();
66
66
 
67
- // Pattern voor zowel /command als $skill
67
+ // Pattern for both /command and $skill
68
68
  const TRIGGER_REGEX = /(^|[\s(])(\/|\$)([a-zA-Z0-9_-]+)/g;
69
69
  const excluded = findExcludedRanges(text);
70
70
 
@@ -96,7 +96,7 @@ export function expandInlineTriggers(text: string): string {
96
96
  // Geen matches -> originele tekst teruggeven
97
97
  if (matches.length === 0) return text;
98
98
 
99
- // Sorteer op positie en verwijder overlappingen
99
+ // Sort by position and drop overlaps
100
100
  matches.sort((a, b) => a.start - b.start);
101
101
  const deduped: typeof matches = [];
102
102
  let lastEnd = -1;
@@ -107,7 +107,7 @@ export function expandInlineTriggers(text: string): string {
107
107
  }
108
108
  }
109
109
 
110
- // Bouw nieuwe tekst op met expansies
110
+ // Rebuild the text with expansions
111
111
  let result = "";
112
112
  let cursor = 0;
113
113
 
@@ -160,7 +160,7 @@ export function setupInlineInvocation(
160
160
  return { action: "continue" };
161
161
  });
162
162
 
163
- // Cache verversen bij herstart/reload zodat nieuwe skills meteen zichtbaar zijn
163
+ // Refresh the cache on restart/reload so new skills show up immediately
164
164
  pi.on("session_start", () => {
165
165
  invalidateSkillCache();
166
166
  });
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * skill-manager.ts
3
3
  * ---------------------------------------------------------------------------
4
- * Skills Manager v2 — volledig eigen TUI-overlay (lijst ⇄ detail) met:
5
- * - zoeken dat echt filtert (type=filter, ctrl+u wist)
6
- * - categorie-koppen (global/project/prompts/extra) + tab-categoriefilter
7
- * - sorteren op naam of gebruik (s), usage-counts uit de ledger
8
- * - detail-paneel: metadata, frontmatter, body met scroll
9
- * - e=bewerken (edit-commando in de editor via pi's !-flow),
10
- * n=nieuwe skill, d=verwijderen met confirm
4
+ * Skills Manager v2 — list ⇄ detail overlay:
5
+ * - search that actually filters (type=filter, ctrl+u clears)
6
+ * - category headers (global/project/prompts/extra) + tab category filter
7
+ * - sort by name or usage (s), usage counts from the ledger
8
+ * - detail panel: metadata, frontmatter, body with scroll
9
+ * - e=edit (edit command in the editor via pi's ! flow),
10
+ * n=new skill, d=delete with confirm
11
11
  * ---------------------------------------------------------------------------
12
12
  */
13
13
 
@@ -30,7 +30,7 @@ import { runSkillDoctor } from "./skill-doctor.ts";
30
30
  import { runSkillsNew } from "./skill-templates.ts";
31
31
 
32
32
  const CATEGORY_LABELS: Record<SkillCategory | "all", string> = {
33
- all: "alles",
33
+ all: "all",
34
34
  global: "global",
35
35
  project: "project",
36
36
  prompts: "prompts",
@@ -59,18 +59,18 @@ function formatBytes(n: number): string {
59
59
  }
60
60
 
61
61
  function formatLastUsed(ms: number): string {
62
- if (!ms) return "nooit";
62
+ if (!ms) return "never";
63
63
  const diff = Date.now() - ms;
64
64
  const min = Math.floor(diff / 60_000);
65
- if (min < 1) return "zojuist";
66
- if (min < 60) return `${min}m geleden`;
65
+ if (min < 1) return "just now";
66
+ if (min < 60) return `${min}m ago`;
67
67
  const h = Math.floor(min / 60);
68
- if (h < 24) return `${h}u geleden`;
68
+ if (h < 24) return `${h}h ago`;
69
69
  const d = Math.floor(h / 24);
70
- return `${d}d geleden`;
70
+ return `${d}d ago`;
71
71
  }
72
72
 
73
- /** Voeg tekst toe aan de editor als nieuwe regel; de gebruiker drukt enter. */
73
+ /** Append text as a new editor line; the user presses enter to run it. */
74
74
  function appendToEditor(ctx: any, text: string, notify: string): void {
75
75
  const current = ctx.ui.getEditorText?.() ?? "";
76
76
  const separator = current && !current.endsWith("\n") ? "\n" : "";
@@ -78,8 +78,8 @@ function appendToEditor(ctx: any, text: string, notify: string): void {
78
78
  ctx.ui.notify(notify, "info");
79
79
  }
80
80
 
81
- /** POSIX single-quote een pad zodat een skill-naam met shell-metatekens
82
- * (bv. `a; curl … | sh`, uit een untrusted gecloonde repo) niet injecteert. */
81
+ /** POSIX single-quote a path so a skill name with shell metacharacters
82
+ * (e.g. `a; curl … | sh` from an untrusted cloned repo) cannot inject. */
83
83
  function shellQuote(value: string): string {
84
84
  return `'${value.replace(/'/g, `'"'"'`)}'`;
85
85
  }
@@ -144,10 +144,10 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
144
144
  } else {
145
145
  rmSync(entry.filePath, { force: true });
146
146
  }
147
- ctx.ui.notify(`Skill verwijderd: ${entry.name}`, "info");
147
+ ctx.ui.notify(`Skill deleted: ${entry.name}`, "info");
148
148
  } catch (error) {
149
149
  ctx.ui.notify(
150
- `Verwijderen mislukt: ${error instanceof Error ? error.message : String(error)}`,
150
+ `Delete failed: ${error instanceof Error ? error.message : String(error)}`,
151
151
  "error",
152
152
  );
153
153
  }
@@ -160,7 +160,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
160
160
 
161
161
  const insertBody = (entry: SkillEntry) => {
162
162
  insertSkillBody(ctx, entry.name, readSkillBody(entry.filePath));
163
- ctx.ui.notify("Skill ingevoegd in je prompt", "info");
163
+ ctx.ui.notify("Skill inserted into your prompt", "info");
164
164
  };
165
165
 
166
166
  return {
@@ -171,8 +171,8 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
171
171
 
172
172
  if (mode === "list") {
173
173
  const f = filtered();
174
- // Kop: totaal + sort + live filter
175
- const head = `Skills · ${f.length}/${entries.length} · ${sort === "name" ? "naam" : "gebruik"} · ${CATEGORY_LABELS[category]}${query ? ` · zoek "${query}"` : ""}`;
174
+ // Header: total + sort + live filter
175
+ const head = `Skills · ${f.length}/${entries.length} · ${sort === "name" ? "name" : "usage"} · ${CATEGORY_LABELS[category]}${query ? ` · search "${query}"` : ""}`;
176
176
  lines.push(wrapRow(theme.fg("accent", theme.bold(head)), innerWidth));
177
177
  lines.push(border(`├${"─".repeat(innerWidth)}┤`));
178
178
 
@@ -180,12 +180,12 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
180
180
  const emptyMsg =
181
181
  entries.length === 0 && !query
182
182
  ? "No skills installed — ctrl+n to create one"
183
- : `Geen skills voor "${query}"`;
183
+ : `No skills for "${query}"`;
184
184
  lines.push(
185
185
  wrapRow(theme.fg("warning", emptyMsg), innerWidth),
186
186
  );
187
187
  } else {
188
- // scroll-window rondom de selectie
188
+ // scroll window around the selection
189
189
  const start = Math.max(
190
190
  0,
191
191
  Math.min(selected - Math.floor(LIST_ROWS / 2), f.length - LIST_ROWS),
@@ -238,7 +238,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
238
238
  wrapRow(
239
239
  theme.fg(
240
240
  "warning",
241
- `ctrl+d of enter = verwijderen "${selectedEntry()?.name ?? ""}", andere toets = annuleren`,
241
+ `ctrl+d or enter = delete "${selectedEntry()?.name ?? ""}", any other key cancels`,
242
242
  ),
243
243
  innerWidth,
244
244
  ),
@@ -248,7 +248,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
248
248
  wrapRow(
249
249
  theme.fg(
250
250
  "dim",
251
- "typ=filter · ↑↓ · →/enter=detail · tab=categorie · ctrl+s=sort",
251
+ "type=filter · ↑↓ · →/enter=detail · tab=category · ctrl+s=sort",
252
252
  ),
253
253
  innerWidth,
254
254
  ),
@@ -257,7 +257,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
257
257
  wrapRow(
258
258
  theme.fg(
259
259
  "dim",
260
- "ctrl+e=bewerken · ctrl+n=nieuw · ctrl+d=verwijderen · esc=sluiten",
260
+ "ctrl+e=edit · ctrl+n=new · ctrl+d=delete · esc=close",
261
261
  ),
262
262
  innerWidth,
263
263
  ),
@@ -283,15 +283,15 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
283
283
  );
284
284
  lines.push(
285
285
  meta(
286
- "gebruikt",
287
- `${u?.count ?? 0}× · laatste ${formatLastUsed(u?.lastUsed ?? 0)}`,
286
+ "used",
287
+ `${u?.count ?? 0}× · last ${formatLastUsed(u?.lastUsed ?? 0)}`,
288
288
  ),
289
289
  );
290
- lines.push(meta("bestand", e.filePath));
290
+ lines.push(meta("file", e.filePath));
291
291
  lines.push(
292
292
  meta(
293
- "omvang",
294
- `${formatBytes(e.sizeBytes)} · ${e.lineCount} regels · gewijzigd ${formatLastUsed(e.mtimeMs)}`,
293
+ "size",
294
+ `${formatBytes(e.sizeBytes)} · ${e.lineCount} lines · changed ${formatLastUsed(e.mtimeMs)}`,
295
295
  ),
296
296
  );
297
297
  lines.push(
@@ -299,13 +299,13 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
299
299
  "frontmatter",
300
300
  e.frontmatterKeys.length
301
301
  ? e.frontmatterKeys.join(", ")
302
- : "(geen)",
302
+ : "(none)",
303
303
  ),
304
304
  );
305
305
  if (e.disableModelInvocation) {
306
306
  lines.push(
307
307
  wrapRow(
308
- theme.fg("warning", "model-invocatie uitalleen handmatig"),
308
+ theme.fg("warning", "model invocation off insert only"),
309
309
  innerWidth,
310
310
  ),
311
311
  );
@@ -316,7 +316,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
316
316
  lines.push(border(`├${"─".repeat(innerWidth)}┤`));
317
317
  const shown = bodyLines.slice(scroll, scroll + DETAIL_ROWS);
318
318
  if (shown.length === 0 || shown.join("").trim() === "") {
319
- lines.push(wrapRow(theme.fg("dim", "(lege body)"), innerWidth));
319
+ lines.push(wrapRow(theme.fg("dim", "(empty body)"), innerWidth));
320
320
  }
321
321
  for (const line of shown) {
322
322
  lines.push(wrapRow(theme.fg("text", line || " "), innerWidth));
@@ -325,7 +325,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
325
325
  if (confirmDelete) {
326
326
  lines.push(
327
327
  wrapRow(
328
- theme.fg("warning", `ctrl+d of enter = verwijderen "${e.name}", andere toets = annuleren`),
328
+ theme.fg("warning", `ctrl+d or enter = delete "${e.name}", any other key cancels`),
329
329
  innerWidth,
330
330
  ),
331
331
  );
@@ -334,7 +334,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
334
334
  wrapRow(
335
335
  theme.fg(
336
336
  "dim",
337
- "↑↓ scroll · enter=invoegen · ctrl+e=bewerken · ctrl+d=verwijderen · ←/esc=terug",
337
+ "↑↓ scroll · enter=insert · ctrl+e=edit · ctrl+d=delete · ←/esc=back",
338
338
  ),
339
339
  innerWidth,
340
340
  ),
@@ -356,7 +356,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
356
356
  if (mode === "list") {
357
357
  const f = filtered();
358
358
  if (confirmDelete) {
359
- // Bevestig verwijderen: ctrl+d of enter; elke andere toets annuleert.
359
+ // Confirm delete: ctrl+d or enter; any other key cancels.
360
360
  if (data === "\x04" || matchesKey(data, "enter")) {
361
361
  const entry = selectedEntry();
362
362
  if (entry) doDelete(entry);
@@ -375,17 +375,17 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
375
375
  category = CATEGORY_ORDER[(idx + 1) % CATEGORY_ORDER.length]!;
376
376
  selected = 0;
377
377
  } else if (data === "\x13") {
378
- // ctrl+s = sorteren
378
+ // ctrl+s = sort
379
379
  sort = sort === "name" ? "usage" : "name";
380
380
  selected = 0;
381
381
  } else if (data === "\x05") {
382
- // ctrl+e = bewerken
382
+ // ctrl+e = edit
383
383
  const entry = selectedEntry();
384
384
  if (entry) {
385
385
  appendToEditor(
386
386
  ctx,
387
387
  editorCommand(entry.filePath),
388
- "Edit-commando in de editor — enter draait 'm",
388
+ "Edit command in the editor — press enter to run it",
389
389
  );
390
390
  close();
391
391
  return;
@@ -395,7 +395,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
395
395
  done("new");
396
396
  return;
397
397
  } else if (data === "\x04") {
398
- // ctrl+d = verwijderen (met confirm)
398
+ // ctrl+d = delete (with confirm)
399
399
  if (selectedEntry()) confirmDelete = true;
400
400
  } else if (matchesKey(data, "enter") || matchesKey(data, "right")) {
401
401
  const entry = selectedEntry();
@@ -405,7 +405,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
405
405
  matchesKey(data, "backspace") ||
406
406
  data === "\x15"
407
407
  ) {
408
- // typen filtert; ctrl+u wist het filter
408
+ // typing filters; ctrl+u clears the filter
409
409
  if (data === "\x15") query = "";
410
410
  else if (matchesKey(data, "backspace")) query = query.slice(0, -1);
411
411
  else query += data;
@@ -429,16 +429,16 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
429
429
  scroll + 1,
430
430
  );
431
431
  } else if (data === "\x05") {
432
- // ctrl+e = bewerken
432
+ // ctrl+e = edit
433
433
  appendToEditor(
434
434
  ctx,
435
435
  editorCommand(detail!.filePath),
436
- "Edit-commando in de editor — enter draait 'm",
436
+ "Edit command in the editor — press enter to run it",
437
437
  );
438
438
  close();
439
439
  return;
440
440
  } else if (data === "\x04") {
441
- // ctrl+d = verwijderen
441
+ // ctrl+d = delete
442
442
  confirmDelete = true;
443
443
  } else if (matchesKey(data, "enter") || matchesKey(data, "tab")) {
444
444
  insertBody(detail!);
@@ -460,7 +460,7 @@ export async function showSkillManager(ctx: any): Promise<"new" | null> {
460
460
  );
461
461
  }
462
462
 
463
- /** Registreer de `/skills` command. */
463
+ /** Register the `/skills` command. */
464
464
  export type SkillManagerCommandDeps = {
465
465
  runDoctor?: (ctx: any) => Promise<void>;
466
466
  };
@@ -493,5 +493,5 @@ export function registerSkillManagerCommand(
493
493
  });
494
494
  }
495
495
 
496
- // Re-export voor compatibiliteit met bestaande importers/tests.
496
+ // Re-export for existing importers/tests.
497
497
  export { listSkills, readSkillBody } from "./skill-registry.ts";
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * skill-registry.ts
3
3
  * ---------------------------------------------------------------------------
4
- * Data-laag voor de skills manager v2: discovery via pi core's eigen
5
- * `loadSkills()` (recursieve SKILL.md-discovery, loose .md children,
6
- * SourceInfo, diagnostics) plus rijke metadata per skill en een
7
- * usage-ledger. Cache met TTL + expliciete invalidatie (session_start,
8
- * manager-open) zodat nieuwe skills zonder herstart zichtbaar worden.
4
+ * Data layer for skills manager v2: discovery via pi core `loadSkills()`
5
+ * (recursive SKILL.md discovery, loose .md children, SourceInfo,
6
+ * diagnostics) plus rich per-skill metadata and a usage ledger. Cache with
7
+ * TTL + explicit invalidation (session_start, manager-open) so new skills
8
+ * show up without a restart.
9
9
  * ---------------------------------------------------------------------------
10
10
  */
11
11
 
@@ -15,7 +15,7 @@ import { basename, dirname, join, relative } from "node:path";
15
15
  import { getAgentDir, getAgentPath } from "../../paths/agent-dirs.ts";
16
16
  import { parseSkillFrontmatter, stripFrontmatter } from "../../core/frontmatter.ts";
17
17
 
18
- /** Categorie: waar de skill vandaan komt. */
18
+ /** Category: where the skill came from. */
19
19
  export type SkillCategory = "global" | "project" | "prompts" | "extra";
20
20
 
21
21
  export interface SkillUsage {
@@ -28,22 +28,22 @@ export interface SkillEntry {
28
28
  description: string;
29
29
  filePath: string;
30
30
  baseDir: string;
31
- /** Alleen true voor core skills die een eigen skill-directory bezitten. */
31
+ /** True only for core skills that own a skill directory. */
32
32
  isDirectorySkill?: boolean;
33
33
  category: SkillCategory;
34
34
  disableModelInvocation: boolean;
35
35
  sizeBytes: number;
36
36
  lineCount: number;
37
37
  mtimeMs: number;
38
- /** Alle frontmatter-keys (raw, inclusief onbekende). */
38
+ /** All frontmatter keys (raw, including unknown). */
39
39
  frontmatterKeys: string[];
40
- /** Diagnostische melding (core diagnostics, of lege description). */
40
+ /** Diagnostic message (core diagnostics, or empty description). */
41
41
  warning?: string;
42
42
  }
43
43
 
44
- /** Losse .md/.txt in extra-dirs (prompts/legacy) die core weigert (geen
45
- * frontmatter-description). Core-dirs houden core's strenge regels; alleen
46
- * de extra dirs krijgen deze lakse fallback zodat oude prompts blijven werken. */
44
+ /** Loose .md/.txt in extra dirs (prompts/legacy) that core rejects (no
45
+ * frontmatter description). Core dirs keep core's strict rules; only extra
46
+ * dirs get this lax fallback so old prompts keep working. */
47
47
  function scanLooseExtraFiles(
48
48
  extras: { path: string; category: SkillCategory }[],
49
49
  knownPaths: Set<string>,
@@ -82,7 +82,7 @@ function scanLooseExtraFiles(
82
82
  lineCount: content.split("\n").length,
83
83
  mtimeMs,
84
84
  frontmatterKeys: parseFrontmatterKeys(content),
85
- warning: "geen description — model ziet deze skill niet in de prompt",
85
+ warning: "no description — the model will not see this skill in the prompt",
86
86
  });
87
87
  knownPaths.add(filePath);
88
88
  }
@@ -105,7 +105,7 @@ export function setSkillCacheInvalidationHandler(handler: (() => void) | null):
105
105
  const usageCache = new Map<string, SkillUsage>();
106
106
  let usageLoaded = false;
107
107
 
108
- /** Verwijder de discovery-cache (volgende leesactie scant opnieuw). */
108
+ /** Drop the discovery cache (the next read scans again). */
109
109
  export function invalidateSkillCache(): void {
110
110
  cachedAt = 0;
111
111
  cachedEntries = null;
@@ -114,7 +114,7 @@ export function invalidateSkillCache(): void {
114
114
  onCacheInvalidated?.();
115
115
  }
116
116
 
117
- /** Legacy prompts/loose-md dirs die inline-invocation altijd al scanden. */
117
+ /** Legacy prompts/loose-md dirs that inline-invocation already scanned. */
118
118
  function extraSkillPaths(cwd: string): { path: string; category: SkillCategory }[] {
119
119
  const agent = getAgentDir();
120
120
  return [
@@ -144,7 +144,7 @@ function categorize(
144
144
  return "extra";
145
145
  }
146
146
 
147
- /** Parse alle top-level frontmatter-keys (naam + waarde-indicator niet nodig). */
147
+ /** Parse all top-level frontmatter keys (name only; value is unused). */
148
148
  function parseFrontmatterKeys(content: string): string[] {
149
149
  const lines = content.split("\n");
150
150
  if (lines[0]?.trim() !== "---") return [];
@@ -275,10 +275,10 @@ export function loadSkillCatalog(cwd: string = process.cwd()): SkillEntry[] {
275
275
  lineCount = content.split("\n").length;
276
276
  mtimeMs = statSync(s.filePath).mtimeMs;
277
277
  } catch {
278
- warning = "bestand niet leesbaar";
278
+ warning = "file not readable";
279
279
  }
280
280
  if (!warning && !s.description.trim()) {
281
- warning = "geen description — model ziet deze skill niet in de prompt";
281
+ warning = "no description — the model will not see this skill in the prompt";
282
282
  }
283
283
  if (!warning && s.filePath && diagByPath.has(s.filePath)) {
284
284
  warning = diagByPath.get(s.filePath);
@@ -299,7 +299,7 @@ export function loadSkillCatalog(cwd: string = process.cwd()): SkillEntry[] {
299
299
  };
300
300
  });
301
301
 
302
- // loose entries achteraan; bij naam-collisie wint core
302
+ // loose entries last; on a name collision core wins
303
303
  const looseNames = new Set(loose.map((e) => e.name));
304
304
  const catalogPaths = new Set([
305
305
  ...entries.map((e) => e.filePath),
@@ -322,7 +322,7 @@ export function loadSkillCatalog(cwd: string = process.cwd()): SkillEntry[] {
322
322
  return cachedEntries;
323
323
  }
324
324
 
325
- /** Compat: naambestandspad (voor inline-invocation). */
325
+ /** Compat: namefile path (for inline-invocation). */
326
326
  export function getAvailableSkills(): Map<string, string> {
327
327
  loadSkillCatalog();
328
328
  return cachedPathMap ?? new Map();
@@ -346,11 +346,11 @@ function loadUsage(): void {
346
346
  usageCache.set(name, { count: u.count ?? 0, lastUsed: u.lastUsed ?? 0 });
347
347
  }
348
348
  } catch {
349
- // geen bestand of kapot JSON → lege ledger
349
+ // missing file or broken JSON → empty ledger
350
350
  }
351
351
  }
352
352
 
353
- /** Usage voor alle skills (naam → {count, lastUsed}). */
353
+ /** Usage for every skill (name → {count, lastUsed}). */
354
354
  export function getSkillUsage(): Map<string, SkillUsage> {
355
355
  loadUsage();
356
356
  return usageCache;
@@ -359,7 +359,7 @@ export function getSkillUsage(): Map<string, SkillUsage> {
359
359
  let usageFlushTimer: ReturnType<typeof setTimeout> | null = null;
360
360
  let exitFlushRegistered = false;
361
361
 
362
- /** Schrijf de ledger nu naar schijf (best-effort, sync). */
362
+ /** Write the ledger to disk now (best-effort, sync). */
363
363
  export function flushSkillUsage(): void {
364
364
  if (usageFlushTimer) {
365
365
  clearTimeout(usageFlushTimer);
@@ -371,14 +371,14 @@ export function flushSkillUsage(): void {
371
371
  mkdirSync(getAgentDir(), { recursive: true });
372
372
  writeFileSync(usageFile(), JSON.stringify(obj, null, 2), "utf8");
373
373
  } catch {
374
- // best-effort: tracking mag de hot path nooit breken
374
+ // best-effort: tracking must never break the hot path
375
375
  }
376
376
  }
377
377
 
378
378
  /**
379
- * Log een skill-gebruik. De in-memory ledger wordt meteen bijgewerkt (zodat
380
- * getSkillUsage() klopt), maar de schijf-write wordt gedebounced zodat de
381
- * input hot path niet per keystroke een sync writeFileSync doet.
379
+ * Log a skill use. The in-memory ledger updates immediately (so
380
+ * getSkillUsage() is correct), but the disk write is debounced so the
381
+ * input hot path does not do a sync writeFileSync per keystroke.
382
382
  */
383
383
  export function recordSkillUsage(name: string): void {
384
384
  loadUsage();
@@ -436,7 +436,7 @@ export function listSkills(): { name: string; description: string; path: string;
436
436
  }));
437
437
  }
438
438
 
439
- /** Pure filter/sorteer-logica voor de manager-lijst (testbaar zonder TUI). */
439
+ /** Pure filter/sort for the manager list (testable without a TUI). */
440
440
  export function applySkillFilter(
441
441
  entries: SkillEntry[],
442
442
  query: string,
@@ -84,7 +84,7 @@ function listForQuery(
84
84
  ? filtered
85
85
  : [
86
86
  {
87
- label: `geen match voor '${query}'`,
87
+ label: `no match for '${query}'`,
88
88
  value: NONE_VALUE,
89
89
  },
90
90
  ];