aizuchi 0.4.0 → 0.7.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/CHANGELOG.md +139 -0
- package/README.ja.md +213 -0
- package/README.md +232 -170
- package/dist/bin/aizuchi.js +48 -11
- package/dist/server/app.js +139 -11
- package/dist/server/lib/docs.js +209 -2
- package/dist/server/lib/files.js +18 -4
- package/dist/server/lib/fs-safe.js +81 -21
- package/dist/server/lib/proposal.js +35 -4
- package/dist/server/lib/related-docs-validate.js +37 -0
- package/dist/server/lib/task-ops.js +31 -7
- package/dist/server/lib/trash.js +21 -0
- package/dist/shared/activity-heatmap.js +39 -0
- package/dist/shared/ai-health.js +78 -0
- package/dist/shared/related-docs.js +5 -59
- package/dist/shared/schema.js +27 -5
- package/dist/shared/stats.js +17 -8
- package/dist/shared/task-setters.js +144 -0
- package/dist/web/assets/index-CiZ-pdUZ.css +1 -0
- package/dist/web/assets/index-U2se0dzu.js +99 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -3
- package/templates/claude-rules.md +5 -0
- package/dist/web/assets/index-BYfu8Hdw.js +0 -93
- package/dist/web/assets/index-DiMXsTSP.css +0 -1
package/README.md
CHANGED
|
@@ -1,211 +1,273 @@
|
|
|
1
|
+
English | [日本語](README.ja.md)
|
|
2
|
+
|
|
1
3
|
# AIzuchi
|
|
2
4
|
|
|
3
|
-
**AI
|
|
5
|
+
**A local kanban dashboard for the work you delegate to AI coding agents** (npm / CLI name: `aizuchi`)
|
|
4
6
|
|
|
5
|
-
CLI
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
You hand work to a CLI-based AI coding agent (Claude Code and the like), then watch and steer its
|
|
8
|
+
progress on a kanban board in your browser. Tasks are plain text under `.kanban/`
|
|
9
|
+
(Markdown + YAML frontmatter) — that file is the single source of truth, and both the AI and you
|
|
10
|
+
take part by simply reading and writing files.
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
The name comes from *aizuchi* (相槌) — the small back-channel cues that let two people stay in sync
|
|
13
|
+
without spelling everything out: the quiet understanding between you and your AI.
|
|
11
14
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
+
- **Local only** — binds to `127.0.0.1` only. No outbound requests, no telemetry, ever.
|
|
16
|
+
- **Files are the source of truth** — no database. Everything is git-trackable and still readable
|
|
17
|
+
while the server is stopped.
|
|
18
|
+
- **Convention-based AI integration** — the AI needs no special API or SDK; it joins by reading and
|
|
19
|
+
writing the same files you do.
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
See [CHANGELOG.md](CHANGELOG.md) for the version history.
|
|
17
22
|
|
|
18
|
-
##
|
|
23
|
+
## Requirements
|
|
19
24
|
|
|
20
|
-
- Node.js **20.1
|
|
21
|
-
- macOS / Linux / WSL
|
|
25
|
+
- Node.js **20.1 or newer** (uses the `recursive` option of `fs.readdir`)
|
|
26
|
+
- macOS / Linux / WSL (native Windows is best-effort)
|
|
22
27
|
|
|
23
|
-
##
|
|
28
|
+
## Quick start
|
|
24
29
|
|
|
25
30
|
```bash
|
|
26
|
-
#
|
|
27
|
-
npx aizuchi init # .kanban/
|
|
28
|
-
npx aizuchi # = serve
|
|
31
|
+
# Run from the root of the project you want to manage
|
|
32
|
+
npx aizuchi init # creates .kanban/ and appends the operating rules to CLAUDE.md (marker-delimited)
|
|
33
|
+
npx aizuchi # = serve. Opens the board at http://127.0.0.1:5151 (Ctrl+C to stop)
|
|
29
34
|
|
|
30
|
-
#
|
|
31
|
-
npx aizuchi overview <dir...> #
|
|
35
|
+
# View several projects at once (read-only aggregation, default port 5152)
|
|
36
|
+
npx aizuchi overview <dir...> # e.g. npx aizuchi overview ~/proj-a ~/proj-b
|
|
32
37
|
```
|
|
33
38
|
|
|
34
|
-
- `init`
|
|
35
|
-
CLAUDE.md
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- **
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
|
|
39
|
+
- `init` never clobbers existing files. If `.kanban/` already exists, it stops safely.
|
|
40
|
+
It appends to CLAUDE.md inside markers (`<!-- aizuchi:begin -->` … `<!-- aizuchi:end -->`) and on
|
|
41
|
+
re-run replaces only that section (idempotent). To remove it, delete `.kanban/` and the marked
|
|
42
|
+
section — that's all.
|
|
43
|
+
- `--port <n>` changes the port, `--member <id>` sets the AI's member ID (added with the `ai` kind
|
|
44
|
+
if it isn't already in `config.members`), and `--yes` skips the confirmation prompt.
|
|
45
|
+
- `overview` aggregates each directory's `.kanban` read-only (no editing, no creating tasks, no
|
|
46
|
+
drag & drop). Directories without a `.kanban`, or the same directory passed twice, are rejected
|
|
47
|
+
with a clear error at startup. The default port is 5152 so it doesn't collide with `serve`
|
|
48
|
+
(5151); change it with `--port <n>`.
|
|
49
|
+
- It binds to `127.0.0.1` only and makes no outbound network requests whatsoever.
|
|
50
|
+
|
|
51
|
+
## What it does
|
|
52
|
+
|
|
53
|
+
The board lets you see and steer everything you've delegated to the AI. When your editor or the AI
|
|
54
|
+
rewrites a file, the change reaches any open board within about a second (live updates via
|
|
55
|
+
watcher → SSE). The UI language can be switched between English and Japanese (`en` / `ja`, default
|
|
56
|
+
`en`).
|
|
57
|
+
|
|
58
|
+
### Board operations
|
|
59
|
+
|
|
60
|
+
- **Drag & drop** — moving a card between columns pinpoint-writes only the `status` / `updated`
|
|
61
|
+
frontmatter fields (the body is preserved). Moving to Pending opens a dialog asking for the
|
|
62
|
+
block kind (decision / external / dependency) and a reason.
|
|
63
|
+
- **Search filter** — narrow cards by title, label, or assignee.
|
|
64
|
+
- **Saved views** — store filter conditions by name in the config and apply them in one click from
|
|
65
|
+
a chip.
|
|
66
|
+
- **"Assigned to me" filter** — pick yourself (a member) in the header to narrow the board, in one
|
|
67
|
+
tap, to just the tasks coming your way: ones assigned to you, ones that `@mention` you in the body,
|
|
68
|
+
and ones you created that are awaiting approval (`proposed`). Handy for checking your own queue
|
|
69
|
+
first thing in the morning when several people / AIs are working at once. The choice is stored in
|
|
70
|
+
this browser only and never touches the task files.
|
|
71
|
+
- **Pin / focus** — pin a task you want to keep an eye on from its card or detail with 📌, and it
|
|
72
|
+
stays at the top of Home. Pins to tasks that become done or archived hide themselves automatically.
|
|
73
|
+
Separate from the auto-computed critical path, you can always see the tasks you chose to watch. The
|
|
74
|
+
pin set is kept in this browser only.
|
|
75
|
+
- **New-activity highlight** — tasks the AI moved while you weren't looking (status change, new work
|
|
76
|
+
log entry, brand-new task) are surfaced with a card highlight and a "new" badge. Opening a card
|
|
77
|
+
marks it read; the header offers mark-all-read.
|
|
78
|
+
- **Stale badge** — a badge appears on tasks that haven't been updated for a while (the threshold is
|
|
79
|
+
configurable per status).
|
|
80
|
+
- **Send back (ask for a redo)** — from a card or its detail, return a task to `todo` with a reason
|
|
81
|
+
that's recorded in the work log. The most recent send-back reason is auto-included in the
|
|
82
|
+
re-delegation instruction copy.
|
|
83
|
+
- **Quick comment from a card** — add a one-line comment straight to the exchanges (two-way
|
|
84
|
+
comments) section from a card, without opening the detail panel, so the back-and-forth with the AI
|
|
85
|
+
gets one step faster.
|
|
86
|
+
- **Batch instruction copy for multiple selections** — in multi-select mode, pick several cards and
|
|
87
|
+
copy a single instruction that orders them topologically by dependency, so you can batch-delegate
|
|
88
|
+
to the AI in one action.
|
|
89
|
+
- **Drag-to-pan** — grab the empty area of a column to scroll horizontally (interactions starting
|
|
90
|
+
from cards and other interactive elements are excluded).
|
|
91
|
+
|
|
92
|
+
### Visibility
|
|
93
|
+
|
|
94
|
+
- **Critical path** — lays out the `depends_on` DAG in columns by topological depth and highlights
|
|
95
|
+
the critical path. Cycles are detected and warned about, with a reorganization instruction copy
|
|
96
|
+
provided.
|
|
97
|
+
- **Milestones** — a two-pane view of the schedule (per milestone, with done/total) and the history
|
|
98
|
+
of completed work (by month).
|
|
99
|
+
- **Daily digest** — work-log entries grouped and shown by date.
|
|
100
|
+
- **Stats** — completion-count trend, cycle time, time-in-status, completions by member,
|
|
101
|
+
bottlenecks, and a list of stalled (quiet) tasks.
|
|
102
|
+
- **AI activity heatmap** — the stats view also aggregates work-log timestamps into a weekday × hour
|
|
103
|
+
heatmap, so you can see at a glance when the AI tends to be working (display only).
|
|
104
|
+
|
|
105
|
+
### Management and health
|
|
106
|
+
|
|
107
|
+
- **Archive search + restore** — move done tasks into the archive to list, search, and preview.
|
|
108
|
+
"Return to board" restores them to `tasks/` (unarchive).
|
|
109
|
+
- **Trash (.trash backup + restore)** — a safety net that automatically backs up the old version to
|
|
110
|
+
`.kanban/.trash/` just before a task is destructively rewritten. You can list, preview the diff
|
|
111
|
+
against the current version, and restore (recovery from accidental overwrites).
|
|
112
|
+
- **Permanent task deletion** — the task detail offers "Delete permanently" (archive stays the
|
|
113
|
+
primary choice; delete is separated out as a dangerous operation, with a confirmation). A task
|
|
114
|
+
being deleted is always backed up to the trash (`.kanban/.trash`) before it is removed, so it can
|
|
115
|
+
be restored later. If the backup fails, the deletion is aborted (to prevent irreversible loss).
|
|
116
|
+
- **AI config file management** — edit files allowed via `aiConfigPaths` (CLAUDE.md, settings, etc.)
|
|
117
|
+
from the GUI, with JSON syntax checking, a `.bak` backup, and permission preservation.
|
|
118
|
+
- **Git history integration (read-only)** — the commits tab in the detail panel shows commit history
|
|
119
|
+
that mentions the task ID. It only runs `git log` read-only — no fetch, pull, or other outbound
|
|
120
|
+
communication.
|
|
121
|
+
|
|
122
|
+
### AI integration rules + CLI
|
|
123
|
+
|
|
124
|
+
- **Convention-based integration** — `init` appends the operating rules (start / progress / done /
|
|
125
|
+
block / create / format) to CLAUDE.md inside markers. The AI takes part by reading and writing
|
|
126
|
+
files, with no special API needed.
|
|
127
|
+
- **Proposal workflow** — when `aiTaskCreation` is `proposal`, AI-created tasks enter as `proposed`
|
|
128
|
+
and reach `todo` only after approval / rejection. Mode violations are surfaced as warnings.
|
|
129
|
+
- **AI-facing CLI (an optional safety valve)** — write commands `aizuchi task add / move <id> <status> /
|
|
130
|
+
done <id> / block <id> --type --reason / log <id> "msg" / comment <id> "msg" / revise <id> --reason`,
|
|
131
|
+
plus read commands `list [--status S] [--assignee M] / show <id> / next` (proposes the next
|
|
132
|
+
actionable tasks in dependency-resolved order). All of these reuse the existing schema validation,
|
|
133
|
+
serialized ID numbering, and line-replacement checks (editing raw files directly still works too;
|
|
134
|
+
`--json` is supported).
|
|
135
|
+
|
|
136
|
+
### Also
|
|
137
|
+
|
|
138
|
+
- **Cross-project overview** — `aizuchi overview <dir...>` aggregates multiple `.kanban` directories
|
|
139
|
+
read-only.
|
|
140
|
+
- **Command palette ⌘K** — switch views, jump to a task by search, create a task, and open settings
|
|
141
|
+
in one action.
|
|
142
|
+
- **Task detail** — Markdown preview, body editing (conflict detection on save → line-diff prompt),
|
|
143
|
+
and a timeline of the work log and exchanges (two-way comments).
|
|
144
|
+
- **Warning banner** — surfaces broken frontmatter, duplicate IDs, unregistered members, and invalid
|
|
145
|
+
config (the board stays up).
|
|
146
|
+
|
|
147
|
+
## Development
|
|
105
148
|
|
|
106
149
|
```bash
|
|
107
|
-
npm install #
|
|
108
|
-
npm run dev #
|
|
109
|
-
npm run build # web
|
|
110
|
-
npm run serve #
|
|
150
|
+
npm install # install dependencies
|
|
151
|
+
npm run dev # dev: Vite + API in a single process (e.g. http://127.0.0.1:5173)
|
|
152
|
+
npm run build # build web into dist/web + compile server/shared into dist
|
|
153
|
+
npm run serve # serve the built dist in one process (= node dist/bin/aizuchi.js serve)
|
|
111
154
|
npm test # vitest
|
|
112
|
-
npm run typecheck # tsc
|
|
155
|
+
npm run typecheck # tsc (server) + tsc -p web/tsconfig.json (web)
|
|
113
156
|
```
|
|
114
157
|
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
-
|
|
158
|
+
- In development, the Hono app is mounted onto Vite's `configureServer`, so `npm run dev` brings up
|
|
159
|
+
both the SPA and the API at once (no proxy, no concurrently).
|
|
160
|
+
- To develop against a different target project, use `AIZUCHI_DEV_ROOT=/path/to/project npm run dev`.
|
|
118
161
|
|
|
119
|
-
##
|
|
162
|
+
## Architecture
|
|
120
163
|
|
|
121
164
|
```
|
|
122
|
-
┌────────────
|
|
123
|
-
|
|
165
|
+
┌──────────── single process (Node.js) ───────┐
|
|
166
|
+
browser ──HTTP──▶│ static serve (dist/web) REST API SSE │
|
|
124
167
|
(React SPA) ◀SSE─│ server/static.ts server/app.ts events │──watch/read/write──▶ .kanban/
|
|
125
|
-
│ chokidar watcher (server/watcher) │ (
|
|
168
|
+
│ chokidar watcher (server/watcher) │ (plain text)
|
|
126
169
|
└────────────────────────────────────────────┘ ▲
|
|
127
|
-
|
|
170
|
+
AI / human edit directly or via the CLI
|
|
128
171
|
```
|
|
129
172
|
|
|
130
173
|
```
|
|
131
174
|
aizuchi/
|
|
132
|
-
├── bin/aizuchi.ts # CLI
|
|
133
|
-
├── server/ # Hono
|
|
134
|
-
│ ├── app.ts # REST API
|
|
135
|
-
│ ├── overview.ts #
|
|
136
|
-
│ ├── board.ts # .kanban
|
|
137
|
-
│ ├── watcher.ts # chokidar
|
|
138
|
-
│ ├── static.ts #
|
|
175
|
+
├── bin/aizuchi.ts # CLI entry (init / serve / task / overview). Compiled to dist/bin/aizuchi.js
|
|
176
|
+
├── server/ # Hono app, watcher, static serving, file I/O
|
|
177
|
+
│ ├── app.ts # REST API (board / tasks / events) + Host validation + onError
|
|
178
|
+
│ ├── overview.ts # read-only aggregation of multiple .kanban (aizuchi overview, default port 5152)
|
|
179
|
+
│ ├── board.ts # .kanban loading, zod validation, warning generation
|
|
180
|
+
│ ├── watcher.ts # chokidar (200ms debounce) → SSE
|
|
181
|
+
│ ├── static.ts # serving the built SPA (traversal rejected)
|
|
139
182
|
│ └── lib/ # task-file / pinpoint / id / fs-safe / time / git / trash / task-ops
|
|
140
|
-
├── web/ # Vite + React SPA
|
|
183
|
+
├── web/ # Vite + React SPA (build output bundled into dist/web)
|
|
141
184
|
│ └── src/ # App / useBoard / components / lib
|
|
142
|
-
├── shared/ # task / config
|
|
143
|
-
├── templates/ # config.json / claude-rules.md
|
|
144
|
-
└── docs/ # decisions.md
|
|
185
|
+
├── shared/ # zod schemas, types, and line LCS diff for task / config (shared by server / web)
|
|
186
|
+
├── templates/ # config.json / claude-rules.md (generated by init)
|
|
187
|
+
└── docs/ # decisions.md and others
|
|
145
188
|
```
|
|
146
189
|
|
|
147
|
-
##
|
|
190
|
+
## Data model (excerpt)
|
|
148
191
|
|
|
149
|
-
-
|
|
192
|
+
- A task = `.kanban/tasks/<ID>.md` (one task per file, **ID = filename**).
|
|
150
193
|
- frontmatter: `id` / `title` / `status` / `assignee` / `created_by` / `priority` / `labels` /
|
|
151
|
-
`blocked_type` / `blocked_reason` / `depends_on` / `milestone` / `created` / `updated
|
|
152
|
-
- `status`: `proposed` / `backlog` / `todo` / `in_progress` / `pending` / `done
|
|
153
|
-
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
`
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
- [ ]
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
- [ ]
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
- [ ]
|
|
176
|
-
|
|
177
|
-
- [ ]
|
|
178
|
-
|
|
179
|
-
- [ ]
|
|
180
|
-
|
|
181
|
-
- [ ]
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
`blocked_type` / `blocked_reason` / `depends_on` / `milestone` / `created` / `updated`.
|
|
195
|
+
- `status`: `proposed` / `backlog` / `todo` / `in_progress` / `pending` / `done`.
|
|
196
|
+
- Timestamps are ISO 8601 (with timezone, e.g. `2026-06-10T10:00:00+09:00`). They are not converted
|
|
197
|
+
to UTC.
|
|
198
|
+
- Configuration lives in `.kanban/config.json` (project name, ID prefix, port, members, columns,
|
|
199
|
+
milestones, task template, saved views, stale thresholds, language, and more). It works on
|
|
200
|
+
defaults with zero configuration.
|
|
201
|
+
|
|
202
|
+
The complete frontmatter schema — including each field's enum values and required conditions — is
|
|
203
|
+
recorded in the operating rules (the frontmatter schema table) that `aizuchi init` appends to
|
|
204
|
+
`CLAUDE.md`. You can check it right after init.
|
|
205
|
+
|
|
206
|
+
## Manual verification checklist (acceptance criteria / DoD)
|
|
207
|
+
|
|
208
|
+
After `npm run build`, run `node dist/bin/aizuchi.js init` → `... serve` in the target project and
|
|
209
|
+
verify in the browser.
|
|
210
|
+
|
|
211
|
+
- [ ] **init**: `node bin/aizuchi.js init` generates `.kanban/` (config.json, tasks/, archive/,
|
|
212
|
+
README) and appends the operating rules to CLAUDE.md inside marker comments (append at the end
|
|
213
|
+
if one exists, re-run replaces only that section, stop safely if `.kanban/` already exists).
|
|
214
|
+
- [ ] **serve**: `node bin/aizuchi.js serve` shows the board at `http://127.0.0.1:5151`. It does not
|
|
215
|
+
bind to anything other than 127.0.0.1.
|
|
216
|
+
- [ ] **create**: creating a task from the add modal generates `tasks/TASK-00X.md` with a sequential
|
|
217
|
+
ID (the creator is chosen from members, and that ID goes into `created_by`).
|
|
218
|
+
- [ ] **drag & drop**: moving between columns changes only the `status` / `updated` frontmatter
|
|
219
|
+
(confirm the body is preserved with `git diff`).
|
|
220
|
+
- [ ] **live updates**: editing a task file directly in your editor is reflected on the board within
|
|
221
|
+
a second.
|
|
222
|
+
- [ ] **block display**: a pending card shows the block kind (color-coded badge) and reason, and an
|
|
223
|
+
in_progress card shows "last updated N minutes ago".
|
|
224
|
+
- [ ] **warnings**: placing a file with broken frontmatter shows up in the warning banner without
|
|
225
|
+
crashing the board.
|
|
226
|
+
- [ ] **detail**: the task detail offers preview, editing, and a work-log timeline. If a save
|
|
227
|
+
conflicts with an external change, an overwrite confirmation (with a diff) appears.
|
|
228
|
+
- [ ] **archive / trash**: archive a done task → restore it from the archive view. Before a
|
|
229
|
+
destructive rewrite the old version is backed up to `.kanban/.trash/` and can be restored from
|
|
230
|
+
the trash.
|
|
231
|
+
- [ ] **network**: no outbound network communication occurs at all while running.
|
|
232
|
+
- [ ] **shutdown**: with the board open in the browser, `Ctrl+C` stops serve promptly.
|
|
233
|
+
- [ ] **reproducibility**: a third party can go from setup to launch using only this README.
|
|
234
|
+
|
|
235
|
+
## Accessibility
|
|
236
|
+
|
|
237
|
+
Keyboard and screen-reader operation is taken into account.
|
|
238
|
+
|
|
239
|
+
- **Keyboard operation** — forms and buttons can be reached and operated with Tab. The command
|
|
240
|
+
palette (⌘K / Ctrl+K) lets you switch views, jump to a task by search, create a task, and open
|
|
241
|
+
settings entirely from the keyboard.
|
|
242
|
+
- **Moving cards with the keyboard** — focus a card, **grab it with Space**, move between columns
|
|
243
|
+
with the arrow keys, and confirm with Space again (Enter opens the card detail). It uses the same
|
|
244
|
+
status-update path as pointer drag & drop, and moving to Pending triggers the block dialog.
|
|
245
|
+
- **Modals** — focus moves inside on open, Tab / Shift+Tab loop within the dialog (focus trap),
|
|
246
|
+
Escape closes it, and focus returns to the original element after closing.
|
|
247
|
+
- **ARIA / roles** — columns, cards, the toolbar, and dialogs carry `role` and `aria-label`.
|
|
248
|
+
|
|
249
|
+
## Tech stack
|
|
196
250
|
|
|
197
251
|
Node.js 20.1+ / Hono + @hono/node-server / chokidar / SSE / React 18 + TypeScript + Vite /
|
|
198
|
-
@dnd-kit / react-markdown + remark-gfm / gray-matter / zod / CSS Modules / vitest
|
|
252
|
+
@dnd-kit / react-markdown + remark-gfm / gray-matter / zod / CSS Modules / vitest.
|
|
253
|
+
|
|
254
|
+
### Dependencies added beyond the core stack, and why
|
|
255
|
+
|
|
256
|
+
- **@hono/node-server** — runs Hono on the Node runtime and statically serves the built SPA.
|
|
257
|
+
- **js-yaml (+ @types/js-yaml)** — injects the `JSON_SCHEMA` engine into gray-matter so that
|
|
258
|
+
timestamps with `+09:00` round-trip as strings instead of being turned into `Date` (avoids the
|
|
259
|
+
transitive dependency on gray-matter's bundled, older 3.x).
|
|
260
|
+
- **@vitejs/plugin-react** — enables React (JSX / Fast Refresh) in Vite.
|
|
261
|
+
- **@dnd-kit/utilities** — part of the @dnd-kit family; assists with the D&D CSS transform.
|
|
262
|
+
- **tsx** — runs the TypeScript server directly during development (production uses the compiled JS
|
|
263
|
+
in dist).
|
|
264
|
+
- **typescript / vitest / @types/\*** — the foundation for type safety and testing.
|
|
199
265
|
|
|
200
|
-
|
|
266
|
+
## Repository
|
|
201
267
|
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
- **@vitejs/plugin-react** — Vite で React(JSX / Fast Refresh)を使うため
|
|
205
|
-
- **@dnd-kit/utilities** — @dnd-kit ファミリ。D&D の CSS transform 補助
|
|
206
|
-
- **tsx** — 開発時に TypeScript サーバーを直接実行するため(本番は dist のコンパイル済み JS を使用)
|
|
207
|
-
- **typescript / vitest / @types/\*** — 型安全とテストの基盤
|
|
268
|
+
- npm package: `aizuchi`
|
|
269
|
+
- Source & issues: <https://github.com/megaphone-tokyo/AIzuchi>
|
|
208
270
|
|
|
209
|
-
##
|
|
271
|
+
## License
|
|
210
272
|
|
|
211
273
|
MIT
|
package/dist/bin/aizuchi.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import readline from 'node:readline';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import { serve } from '@hono/node-server';
|
|
7
|
-
import { DEFAULT_OVERVIEW_PORT, DEFAULT_PORT } from '../shared/schema.js';
|
|
7
|
+
import { DEFAULT_OVERVIEW_PORT, DEFAULT_PORT, PORT_FALLBACK_ATTEMPTS } from '../shared/schema.js';
|
|
8
8
|
import { analyzeCriticalPath } from '../shared/critical-path.js';
|
|
9
9
|
import { createApp } from '../server/app.js';
|
|
10
10
|
import { loadArchive, loadBoard, loadConfig } from '../server/board.js';
|
|
@@ -16,7 +16,7 @@ import { startWatcher } from '../server/watcher.js';
|
|
|
16
16
|
/**
|
|
17
17
|
* AIzuchi CLI (要件 §4.3)。
|
|
18
18
|
* - init : .kanban/ を生成し、CLAUDE.md へ運用規約をマーカー区切りで追記する (N-9: 冪等)
|
|
19
|
-
* - serve: 127.0.0.1 限定でボードを起動する (--port > config > 5151)
|
|
19
|
+
* - serve: 127.0.0.1 限定でボードを起動する (--port > config > 5151。--port 未指定なら使用中時に次の空きへ自動フォールバック)
|
|
20
20
|
* - overview: 複数 .kanban の read-only 集約を起動する (--port > 5152、波 VII)
|
|
21
21
|
*/
|
|
22
22
|
export const MARKER_BEGIN = '<!-- aizuchi:begin -->';
|
|
@@ -159,28 +159,65 @@ export async function runServe(options) {
|
|
|
159
159
|
// .kanban が symlink / 不在: cleanup を skip (containment 外の削除はしない)
|
|
160
160
|
}
|
|
161
161
|
const { config } = await loadConfig(cwd);
|
|
162
|
-
|
|
162
|
+
// --port を明示したらそのポートに固定 (使用中ならエラー終了)。明示しない場合は
|
|
163
|
+
// config.port → 既定 5151 を起点に、使用中なら順に次の空きへフォールバックする
|
|
164
|
+
// (複数プロジェクトを同時に serve してもポート衝突で止まらないように)。
|
|
165
|
+
// 起点は毎回同じで状態を保存しないため、次回起動時に起点が空いていれば必ず起点で起動する。
|
|
166
|
+
const explicitPort = options.port !== undefined;
|
|
167
|
+
const basePort = options.port ?? config.port ?? DEFAULT_PORT;
|
|
168
|
+
const maxAttempts = explicitPort ? 1 : PORT_FALLBACK_ATTEMPTS;
|
|
163
169
|
const bus = createEventBus();
|
|
164
170
|
const { app } = createApp({ projectRoot: cwd, bus });
|
|
165
171
|
attachStaticAssets(app, path.join(packageRoot(), 'dist', 'web'));
|
|
166
172
|
const watcher = startWatcher({ kanbanDir, bus });
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
// 1 ポートぶんの listen 試行。成功で {server, boundPort}、EADDRINUSE で 'busy'、その他は reject。
|
|
174
|
+
// 127.0.0.1 限定でバインドする (N-1)。フォールバックで変えるのはポート番号のみ・host は固定。
|
|
175
|
+
const tryListen = (candidate) => new Promise((resolve, reject) => {
|
|
176
|
+
let settled = false;
|
|
169
177
|
// info.port は実際にバインドされたポート (port=0 のときは OS が選んだ番号が入る)
|
|
170
|
-
const created = serve({ fetch: app.fetch, port, hostname: '127.0.0.1' }, (info) =>
|
|
178
|
+
const created = serve({ fetch: app.fetch, port: candidate, hostname: '127.0.0.1' }, (info) => {
|
|
179
|
+
settled = true;
|
|
180
|
+
resolve({ server: created, boundPort: info.port });
|
|
181
|
+
});
|
|
171
182
|
created.on('error', (error) => {
|
|
183
|
+
if (settled)
|
|
184
|
+
return; // listen 成功後の実行時エラーはここでは扱わない
|
|
172
185
|
if (error.code === 'EADDRINUSE') {
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
try {
|
|
187
|
+
created.close();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
/* バインド失敗した server の close 失敗は無視 */
|
|
191
|
+
}
|
|
192
|
+
resolve('busy');
|
|
175
193
|
}
|
|
176
194
|
else {
|
|
177
195
|
reject(error);
|
|
178
196
|
}
|
|
179
197
|
});
|
|
180
|
-
}).catch(async (error) => {
|
|
181
|
-
await watcher.close();
|
|
182
|
-
throw error;
|
|
183
198
|
});
|
|
199
|
+
let listened = null;
|
|
200
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
201
|
+
const result = await tryListen(basePort + i).catch(async (error) => {
|
|
202
|
+
await watcher.close();
|
|
203
|
+
throw error; // EADDRINUSE 以外 (権限不足など) は即終了
|
|
204
|
+
});
|
|
205
|
+
if (result !== 'busy') {
|
|
206
|
+
listened = result;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (listened === null) {
|
|
211
|
+
await watcher.close();
|
|
212
|
+
if (explicitPort) {
|
|
213
|
+
throw new Error(`ポート ${basePort} は使用中です。別の --port <番号> を指定するか、起動中の AIzuchi を終了してください`);
|
|
214
|
+
}
|
|
215
|
+
throw new Error(`空きポートが見つかりませんでした (${basePort}〜${basePort + maxAttempts - 1} はすべて使用中)。--port <番号> で指定してください`);
|
|
216
|
+
}
|
|
217
|
+
const { server, boundPort } = listened;
|
|
218
|
+
if (!explicitPort && boundPort !== basePort) {
|
|
219
|
+
log(`ポート ${basePort} は使用中のため ${boundPort} で起動しました`);
|
|
220
|
+
}
|
|
184
221
|
log(`AIzuchi: http://127.0.0.1:${boundPort} (project: ${config.projectName})`);
|
|
185
222
|
log('終了するには Ctrl+C');
|
|
186
223
|
return {
|