kaizen-agent 1.2.1 → 1.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 CHANGED
@@ -226,6 +226,62 @@ kaizen Done, and reviewed. Two things found:
226
226
 
227
227
  </details>
228
228
 
229
+ ## The board
230
+
231
+ `kaizen` opens a dashboard; **Board** is the screen where your work lives. Ideas you have
232
+ not started sit on the left, runs sit under the stage they are actually in, and it redraws
233
+ itself as your agents write to disk — a run that moves while you are looking at it moves on
234
+ screen.
235
+
236
+ ```
237
+ Board ~/code/acme-api
238
+
239
+ IDEA PLANNING BUILDING REVIEW DONE
240
+ ────────────────────── ─────────────────── ──────────────── ───────────── ──────────────
241
+ › · rate limit the ◌ nightly digest ● payroll export ● oauth login ○ rename cli
242
+ public API flags
243
+ ● landing page ○ csv import
244
+ · retry failed
245
+ webhooks
246
+
247
+ ↑↓←→ move n new e edit x reject d delete r run a all projects q back
248
+ ● running ◐ stalled ◌ starting ● waiting on you ○ done · idea
249
+ ```
250
+
251
+ **Throw an idea in with `n`** and it lands in `<state.dir>/inbox.md`, one line each, in the
252
+ same format a run's backlog already uses. `e` edits, `x` rejects with a reason, `d` deletes.
253
+
254
+ **`r` starts a run from an idea** — it asks `full` or `lite`, then opens your configured
255
+ agent with the request already typed. The idea retires from the board once a run exists
256
+ that was started from its text, whoever started it, so the same work never appears twice.
257
+ That check spans every project you have opened, because an idea thrown into the global
258
+ inbox becomes a run inside whichever project you launched the agent in.
259
+
260
+ **The dot is what the run is doing.** `●` cyan is running and `◐` is stalled — kaizen
261
+ records no liveness signal anywhere, so "running" means the run's `state.json` has changed
262
+ in the last half hour. A run whose terminal you closed three days ago shows as stalled,
263
+ which is the distinction the board exists to make. `●` amber, with the name in red, is a
264
+ run that cannot move until you decide something; the legend lists only the states actually
265
+ on screen.
266
+
267
+ **A run's stage is kaizen's, not yours.** You cannot drag a card between columns — a board
268
+ that could do that would be lying about what happened. The one exception is `x` on a run
269
+ card, which abandons it: the same transition `/kaizen abort` makes, with your reason
270
+ recorded in the run's approval file.
271
+
272
+ `a` switches between this project and every project kaizen knows about. Below a hundred
273
+ columns the board stacks into a list rather than squeezing five columns into sixty.
274
+
275
+ ### Mouse
276
+
277
+ Off by default. Turn it on in `kaizen settings` under `ui.mouse`, and clicking a row or
278
+ card selects it, clicking it again opens or runs it, and the wheel scrolls — on the board,
279
+ the menu, runs, backlog, projects and settings.
280
+
281
+ It ships off because the trade is real: while the TUI holds the mouse, your terminal cannot
282
+ select text with it, so copying a run id out of the dashboard stops working until you
283
+ leave. Hold **Shift** and drag to select text anyway.
284
+
229
285
  ## Install
230
286
 
231
287
  ### Prerequisites: Bun
@@ -433,6 +489,7 @@ in one can be finished in another. See [adapters.md](https://github.com/hfadhlul
433
489
  | `build.executor` | `subagent` | Who does the work: a separate agent, `inline` in the current one, or `ask` each time |
434
490
  | `auto_fix.min_severity` | `high` | Findings this bad or worse get fixed without asking |
435
491
  | `approvals.plan` | `true` | Stop and show the plan before anything is built |
492
+ | `ui.mouse` | `false` | Click to select, click again to act; while on, the terminal cannot select text |
436
493
 
437
494
  ```bash
438
495
  kaizen settings
package/cli/install.ts CHANGED
@@ -2,9 +2,10 @@
2
2
  import {
3
3
  symlinkSync, mkdirSync, readdirSync, lstatSync, readlinkSync, unlinkSync,
4
4
  existsSync, copyFileSync, readFileSync, appendFileSync, rmSync, writeFileSync,
5
+ statSync, cpSync,
5
6
  } from "node:fs";
6
7
  import { join, dirname, basename } from "node:path";
7
- import { homedir } from "node:os";
8
+ import { homedir, tmpdir } from "node:os";
8
9
  import { applyPreset, detectPreset, PRESETS, type PresetName } from "./settings.ts";
9
10
 
10
11
  const home = homedir();
@@ -319,8 +320,18 @@ async function resolveRepo() {
319
320
  }
320
321
  return dest;
321
322
  }
322
- step(`cloning into ${tilde(dest)}`);
323
- await Bun.$`git clone --quiet ${REPO_URL} ${dest}`;
323
+ if (Bun.which("git")) {
324
+ step(`cloning into ${tilde(dest)}`);
325
+ await Bun.$`git clone --quiet ${REPO_URL} ${dest}`;
326
+ return dest;
327
+ }
328
+ // No git: the bunx download already holds everything a clone would, so copy it
329
+ // in place. Upgrades then come from re-running bunx rather than a pull.
330
+ step(`copying into ${tilde(dest)} ${c.dim("(no git)")}`);
331
+ cpSync(here, dest, {
332
+ recursive: true, dereference: true,
333
+ filter: (src) => basename(src) !== "node_modules",
334
+ });
324
335
  return dest;
325
336
  }
326
337
 
@@ -356,10 +367,30 @@ const links = targets.flatMap((a) => [
356
367
  ] : []),
357
368
  ]);
358
369
 
370
+ const isWindows = process.platform === "win32";
371
+
372
+ // Windows only hands out file symlinks to an admin or a Developer Mode box, so
373
+ // there the link is a directory junction (allowed for everyone) or, for a plain
374
+ // file, a copy that is refreshed whenever the source moves ahead.
375
+ function makeLink(src: string, dest: string) {
376
+ if (!isWindows) { symlinkSync(src, dest); return; }
377
+ if (statSync(src).isDirectory()) { symlinkSync(src, dest, "junction"); return; }
378
+ copyFileSync(src, dest);
379
+ }
380
+
381
+ function sameFile(a: string, b: string) {
382
+ try { return readFileSync(a).equals(readFileSync(b)); } catch { return false; }
383
+ }
384
+
359
385
  function linkState(src: string, dest: string) {
360
386
  try {
361
387
  const st = lstatSync(dest);
362
- if (!st.isSymbolicLink()) return "occupied";
388
+ if (!st.isSymbolicLink()) {
389
+ // A copied link on Windows is a real file; it is ours as long as it still
390
+ // matches the source, and stale once the source changes.
391
+ if (isWindows && st.isFile()) return sameFile(src, dest) ? "linked" : "stale";
392
+ return "occupied";
393
+ }
363
394
  return readlinkSync(dest) === src ? "linked" : "stale";
364
395
  } catch {
365
396
  return "missing";
@@ -385,8 +416,8 @@ for (const [src, sub] of links) {
385
416
  continue;
386
417
  }
387
418
  mkdirSync(dir, { recursive: true });
388
- if (state !== "missing") unlinkSync(dest);
389
- symlinkSync(src, dest);
419
+ if (state !== "missing") rmSync(dest, { recursive: true, force: true });
420
+ makeLink(src, dest);
390
421
  wrote++;
391
422
  if (verbose) console.log(c.dim(` link ${name}`));
392
423
  }
@@ -603,7 +634,7 @@ function clean(out: string) {
603
634
  // without re-resolving, so an upgrade that only pulls the clone still leaves the
604
635
  // next `bunx kaizen-agent` running whatever was cached the first time.
605
636
  async function clearBunxCache() {
606
- const tmp = process.env.TMPDIR ?? "/tmp";
637
+ const tmp = process.env.TMPDIR ?? tmpdir();
607
638
  let removed = 0;
608
639
  try {
609
640
  for (const name of readdirSync(tmp)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "1.2.1",
4
- "description": "Staged workflow for AI agents \u2014 code, writing, research, operations: plan, human approval, build, independent review, bounded fix loop.",
3
+ "version": "1.3.0",
4
+ "description": "Staged workflow for AI agents — code, writing, research, operations: plan, human approval, build, independent review, bounded fix loop.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "kaizen-agent": "./cli/install.ts",