@kybird/llm-wiki 0.3.0 → 0.4.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/README.md CHANGED
@@ -47,20 +47,21 @@ That's it. The agent does the rest.
47
47
  | `llm-wiki compile list` | Show raw logs not yet compiled — header date **or** content hash (`compile-state.json`), so same-day appends are caught too |
48
48
  | `llm-wiki compile index` | Rebuild `doc/wiki/index.md` (with aliases and answers), regenerate `compile-state.json`, and sync the QMD index. **This is a "compile complete" declaration** — run it after the wiki-compile skill's phases, not instead of them |
49
49
  | `llm-wiki lint` | Broken links, **evidence back-matching** (hash refs & `### Error` quotes must exist verbatim in `doc/raw/`), uncompiled concepts, metadata, staleness |
50
- | `llm-wiki board` / `board report` | Derived kanban view / dashboard (done:abandoned ratio, trend, QA reverts, waiting queue) |
50
+ | `llm-wiki board` / `board report` | Derived kanban view (text only, no flags) / dashboard (done:abandoned ratio, trend, QA reverts, waiting queue) |
51
51
  | `llm-wiki board video` | Replay `activity.jsonl` into a board timelapse MP4 (requires the `video/` Remotion project; CPU render, no GPU) |
52
- | `llm-wiki card new/show/edit` | Create and edit cards — the CLI is the only writer (sentinel-safe sections) |
52
+ | `llm-wiki monitor [--port <n>]` | Live read-only board view at `http://127.0.0.1:<n>` (default 4747) — who claimed what and for how long (`zcode · 14분`), claim expiry, REVIEW questions, gates/dependency waits, the terminal pile (종결 적체 — newest done/superseded/abandoned with timestamps), and an activity stream. A **milestone panel** (purpose axis, plan.md 3.8) shows in-progress milestones expanded with their member cards and completed ones collapsed with progress click a milestone to read its Goal (the plan's 대의) and full member list. Click any card (columns, terminal pile, activity rows) to read the full card file. Polls every 2s (re-renders only on change); the page renders agent-authored text via `textContent` only. Starting one while **the same board's** monitor already listens is **idempotent** — exit 0, same URL. A port held by **another project's** monitor: default execution walks to the next free port (4747→4748→…, each board gets its own URL; the header shows the project name); an explicit `--port` fails with the owner's name. **`--all`** starts a **fleet view** — every llm-wiki project on this machine as tiles (WIP, review queue, milestone progress, last activity) with each project's full board one click away; projects register themselves in a machine-local registry (`~/.llm-wiki/projects.json`) as they run commands. **The CLI stays the only writer** — every non-GET method gets a 405; the server keeps no state |
53
+ | `llm-wiki card new/show/edit` | Create and edit cards — the CLI is the only writer (sentinel-safe sections). `--kind milestone` creates a milestone card (the plan's 대의 lives in its Goal; not pickable, auto-completes when all members terminate); `--milestone "<title>"` attaches a card to one — membership is frontmatter, milestone progress is derived, never stored (plan.md 3.8) |
53
54
  | `llm-wiki pick --claim <name> [--card <title>]` | Atomically claim the next eligible card (lock, WIP limit, dependencies, claim expiry). `--card` claims a specific card by title — every gate still applies: a blocked pick prints why and leaves all files untouched, an unknown title fails (exit 1) |
54
55
  | `llm-wiki handoff <title> --question "…"` | Park a card for human judgment and release the claim |
55
- | `llm-wiki done <title> --result "…"` | Complete a card — Result is required |
56
+ | `llm-wiki done <title> --result "…"` | Complete a card — Result is required. Completing the last member auto-completes its milestone |
56
57
  | `llm-wiki supersede <title> --by a,b` | Replace a card by children; the parent dissolves into `superseded/` |
57
58
  | `llm-wiki abandon <title> --reason "…"` | Discard — reason required, and auto-logged to `doc/raw/` as anti-pattern material |
58
59
  | `llm-wiki reopen <title> --why "…"` | QA: revert a fake-done card back to doing |
59
60
  | `llm-wiki wait [--for handoff\|done\|any\|stall] [--since <ISO>] [--timeout <s>] [--stall-min <m>] [--json]` | Block until a board event, then exit — **exit codes are the contract: 0 = event (one line to stdout), 2 = timeout (nothing printed), 1 = error**; callers re-arm on 2 and act on 0. `--for` picks the event (`handoff` default; `stall` fires after `--stall-min` minutes of silence, default 20). `--since` (default: now) is checked *before* waiting starts, so events that piled up while the caller was busy are returned immediately. Read-only; timestamp-based (survives wholesale `activity.jsonl` rewrites), skips broken lines, waits for the file to exist, `fs.watch` + 5s poll fallback (junction-safe) |
60
61
 
61
- `search`, `lint`, `compile list|index`, `board`, `pick` accept `--json` (a `{schemaVersion: 1, kind: …}` envelope for scripts and skills). `wait --json` is different by design: it prints the matched event itself as one JSON line (or a `{action: "stall", …}` line), because callers parse that line directly.
62
+ `search`, `lint`, `compile list|index`, `board report`, `pick` accept `--json` (a `{schemaVersion: 1, kind: …}` envelope for scripts and skills). `wait --json` is different by design: it prints the matched event itself as one JSON line (or a `{action: "stall", …}` line), because callers parse that line directly.
62
63
 
63
- Any subcommand accepts `--help`/`-h` anywhere in its args: it prints that command's usage and exits 0 without touching the board — exploring a command must never mutate cards. Every card command also rejects unknown flags (`모르는 플래그`), so a typo'd flag fails loudly instead of silently no-oping.
64
+ Any subcommand accepts `--help`/`-h` anywhere in its args: it prints that command's usage and exits 0 without touching the board — exploring a command must never mutate cards. Every board and card command also rejects unknown flags (`모르는 플래그`), so a typo'd flag fails loudly instead of silently no-oping.
64
65
 
65
66
  ```bash
66
67
  # overnight re-arm loop: wake on the next handoff parked after midnight; silent exit 2 after an hour
package/bin/llm-wiki.js CHANGED
@@ -7,6 +7,7 @@ const { compile } = require('../lib/wiki-compile');
7
7
  const { lint } = require('../lib/wiki-lint');
8
8
  const { init } = require('../lib/init');
9
9
  const kanbanCmd = require('../lib/kanban-cmd');
10
+ const kanbanMonitor = require('../lib/kanban-monitor');
10
11
  const kanbanWait = require('../lib/kanban-wait');
11
12
  const skills = require('../lib/skills');
12
13
  const { findDocRoot } = require('../lib/find-doc-root');
@@ -31,20 +32,32 @@ Skills (git channel — prompt edits without npm publish, plan.md 6.2):
31
32
  [--skill <name>] sync one skill only
32
33
 
33
34
  Kanban (cards are files; CLI is the only writer):
34
- llm-wiki board [--html] [--json] Derived board view (columns, WIP, queue) / static HTML
35
+ llm-wiki board Derived board view (columns, WIP, queue) text only, no flags
35
36
  llm-wiki board report Dashboard (done:abandoned ratio, trend, reverts)
36
37
  llm-wiki board video Timelapse of board activity → MP4 (needs video/ project)
37
- llm-wiki card new "<title>" Create card (--goal, --ac, --depends, --not-before)
38
+ llm-wiki monitor [--port <n>] [--all] Live read-only board view at http://127.0.0.1:<n>
39
+ (default 4747, walks to the next free port past other
40
+ projects' monitors) — claims, elapsed, gates, activity;
41
+ --all = fleet view of EVERY llm-wiki project on this
42
+ machine (tiles + per-project boards); the CLI stays the
43
+ only writer (405 on any write method)
44
+ llm-wiki card new "<title>" Create card (--goal, --ac, --depends, --not-before;
45
+ --kind milestone = milestone card, --milestone "<t>"
46
+ attaches to one — plan-level grouping, plan.md 3.8)
38
47
  llm-wiki card show <title> Print card file
39
48
  llm-wiki card edit <title> Sentinel-safe edits; unknown/blank flags fail, output
40
49
  says what changed (--goal/--plan/--ac/--add-ac/
41
50
  --check-ac/--note/--renew-claim/--depends/
42
- --add-depends/--remove-depends)
51
+ --add-depends/--remove-depends/--milestone)
52
+ --milestone (re)groups an active card under a
53
+ milestone card
43
54
  llm-wiki pick --claim <name> [--card <title>]
44
55
  Atomically claim the next eligible card (locks, WIP, deps);
45
- --card names a specific card — gates are never bypassed
56
+ --card names a specific card — gates are never bypassed;
57
+ milestone cards are never picked (plan.md 3.8)
46
58
  llm-wiki handoff <title> --question "…" Park for human judgment, release claim
47
- llm-wiki done <title> --result "…" Complete (Result required)
59
+ llm-wiki done <title> --result "…" Complete (Result required). Completing the
60
+ last member auto-completes its milestone (3.8)
48
61
  llm-wiki supersede <title> --by a,b Replace by children (parent dissolves)
49
62
  llm-wiki abandon <title> --reason "…" Discard (reason required, never deleted)
50
63
  llm-wiki reopen <title> --why "…" QA: revert a fake-done card to doing
@@ -62,7 +75,7 @@ Optional:
62
75
  command ("autoUpdate": false in llm-wiki.config.json,
63
76
  or LLM_WIKI_NO_AUTO_UPDATE=1, to opt out)
64
77
  --json Machine-readable output: {schemaVersion: 1, kind: ...}
65
- (search, lint, compile list|index)
78
+ (search, lint, compile list|index, board report, pick)
66
79
  LLM_WIKI_ROOT=/path Override doc/ root location
67
80
  llm-wiki.config.json { "projectName": "...", "collections": {...},
68
81
  "hooksPath": "templates/githooks",
@@ -70,10 +83,11 @@ Optional:
70
83
  }
71
84
 
72
85
  const [, , subcommand, ...rest] = process.argv;
73
- // --json/--html은 어느 위치에 와도 플래그로 뽑아낸다 (검색어 문자열에서 제외).
86
+ // --json은 어느 위치에 와도 플래그로 뽑아낸다 (검색어 문자열에서 제외). board는
87
+ // --json/--html을 받지 않는다 — 추출된 사실을 dispatch 너머 명령에 넘겨 명시적으로
88
+ // 실패시킨다(전역 선추출이 validateFlags를 우회하게 두면 조용한 no-op 성공이 된다).
74
89
  const jsonRequested = rest.includes('--json');
75
- const htmlRequested = rest.includes('--html');
76
- const args = rest.filter(a => a !== '--json' && a !== '--html');
90
+ const args = rest.filter(a => a !== '--json');
77
91
 
78
92
  // 가드 — 탐색용 호출이 보드를 건드리지 못하게(2026-09-11·12 사고). 부작용 있는
79
93
  // 서브커맨드를 확인하려고 `pick --help`를 쳤다가 실제로 카드를 집는 일을 원천
@@ -83,7 +97,7 @@ const args = rest.filter(a => a !== '--json' && a !== '--html');
83
97
  // (kanban-cmd의 USAGE)에서 온다. 전용 문자열이 없는 읽기 전용 명령(search 등)은
84
98
  // 전체 사용법으로 대신한다. return으로 자연 종료한다 — Windows 파이프 stdout은
85
99
  // 비동기라 process.exit은 마지막 줄을 지울 수 있다(kanban-wait 교훈).
86
- const KNOWN_SUBCOMMANDS = ['search', 'compile', 'lint', 'init', 'skills', 'board', 'card',
100
+ const KNOWN_SUBCOMMANDS = ['search', 'compile', 'lint', 'init', 'skills', 'board', 'monitor', 'card',
87
101
  'pick', 'handoff', 'done', 'supersede', 'abandon', 'reopen', 'resume', 'wait'];
88
102
  if (KNOWN_SUBCOMMANDS.includes(subcommand) && (args.includes('--help') || args.includes('-h'))) {
89
103
  const usageKey = subcommand === 'card' && ['new', 'show', 'edit'].includes(args[0]) ? `card ${args[0]}` : subcommand;
@@ -125,8 +139,16 @@ switch (subcommand) {
125
139
  break;
126
140
  case 'board':
127
141
  if (args[0] === 'report') kanbanCmd.boardReport({ json: jsonRequested });
128
- else if (args[0] === 'video') kanbanCmd.boardVideo({ rest: args });
129
- else kanbanCmd.boardView({ rest: args, json: jsonRequested, html: htmlRequested });
142
+ else if (args[0] === 'video') kanbanCmd.boardVideo({ rest: args, json: jsonRequested });
143
+ else kanbanCmd.boardView({ rest: args, json: jsonRequested });
144
+ break;
145
+ case 'monitor':
146
+ // auto-update 트리거 목록에 없다 — stdout 첫 줄이 URL 계약이라 배너가 깨면
147
+ // 안 된다(wait 제외와 같은 이유). monitor는 async(멱등 재사용 검사)다.
148
+ kanbanMonitor.monitor({ rest: args, json: jsonRequested }).catch(e => {
149
+ console.error(`✗ ${e.message}`);
150
+ process.exit(1);
151
+ });
130
152
  break;
131
153
  case 'card':
132
154
  kanbanCmd.dispatchCard(args);
@@ -50,6 +50,10 @@ function maybeAutoUpdate() {
50
50
  if (cfg.autoUpdate === false) return; // 옵트아웃 — init 재실행으로만 갱신
51
51
  if (process.env.LLM_WIKI_NO_AUTO_UPDATE === '1') return; // 일회성 끄기
52
52
 
53
+ // 기계의 프로젝트 등록부 — monitor --all(플릿)이 이 머신의 보드들을 아는
54
+ // 길이다. 버전 동기화와 무관하게 매번 갱신한다(마지막 본 시각 포함).
55
+ recordProject(docRoot, cfg);
56
+
53
57
  const sp = stampPath(root);
54
58
  if (readStamp(sp) === PKG_VERSION) return; // 이 버전으로 이미 동기화됨
55
59
 
@@ -76,4 +80,38 @@ function maybeAutoUpdate() {
76
80
  }
77
81
  }
78
82
 
79
- module.exports = { maybeAutoUpdate, stampPath };
83
+ // ── 기계의 프로젝트 등록부 (monitor --all 플릿의 발견 소스) ─────────────────
84
+ // <stateDir>/projects.json — { "<리포 절대경로>": { name, docRoot, lastSeen } }.
85
+ // 능동 명령(maybeAutoUpdate)과 monitor 시작이 매번 갱신한다. 플릿은 파일이
86
+ // 정본인 보드들을 직접 읽으므로, 이 등록부는 "어떤 프로젝트가 이 머신에 사나"의
87
+ // 유일한 목록이다 — 스탬프 해시는 경로를 못 되돌려서 이 용도로 못 쓴다.
88
+ function registryPath() {
89
+ return path.join(stateDir(), 'projects.json');
90
+ }
91
+
92
+ function recordProject(docRoot, cfg) {
93
+ try {
94
+ const root = path.dirname(path.resolve(docRoot));
95
+ const name = (cfg && cfg.projectName) || path.basename(root);
96
+ let data = {};
97
+ try { data = JSON.parse(fs.readFileSync(registryPath(), 'utf8')); } catch { /* 없음 — 첫 등록 */ }
98
+ data[root] = { name, docRoot: path.resolve(docRoot), lastSeen: new Date().toISOString() };
99
+ fs.mkdirSync(stateDir(), { recursive: true });
100
+ fs.writeFileSync(registryPath(), JSON.stringify(data, null, 2) + '\n');
101
+ } catch {
102
+ // 등록부는 부가 기능이다 — 실패가 명령을 막지 않는다(maybeAutoUpdate와 같은 규칙).
103
+ }
104
+ }
105
+
106
+ function readProjects() {
107
+ try {
108
+ const data = JSON.parse(fs.readFileSync(registryPath(), 'utf8'));
109
+ return Object.values(data)
110
+ .filter(p => p && p.docRoot && fs.existsSync(path.join(p.docRoot, 'kanban')))
111
+ .sort((a, b) => String(a.name).localeCompare(String(b.name)));
112
+ } catch {
113
+ return [];
114
+ }
115
+ }
116
+
117
+ module.exports = { maybeAutoUpdate, stampPath, recordProject, readProjects };