@kybird/llm-wiki 0.2.2 → 0.4.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 +23 -10
- package/TROUBLESHOOTING.md +9 -0
- package/bin/llm-wiki.js +100 -12
- package/lib/auto-update.js +79 -0
- package/lib/find-doc-root.js +62 -8
- package/lib/init.js +42 -26
- package/lib/kanban-cmd.js +758 -335
- package/lib/kanban-monitor.js +544 -0
- package/lib/kanban-wait.js +215 -0
- package/lib/kanban.js +89 -13
- package/lib/local-today.js +9 -0
- package/lib/skills.js +351 -0
- package/lib/wiki-compile.js +373 -369
- package/lib/wiki-search.js +41 -39
- package/package.json +5 -2
- package/skills/kanban-plan/SKILL.md +21 -4
- package/skills/work-loop/SKILL.md +22 -2
- package/templates/AGENTS.md +6 -1
package/lib/wiki-search.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// 원본: TTSTextViewer/.agents/skills/wiki-search/scripts/search.js
|
|
3
3
|
// 변경점:
|
|
4
4
|
// - __dirname '../../../../' 4단 종속 제거 → findDocRoot(), findQmd() 사용.
|
|
5
|
-
// - findstr(Windows 전용) → process.platform 분기 (win32=findstr, else=grep).
|
|
6
5
|
// - 2026-08-29 (개선계획 0-1): "QMD 성공 시 grep 건너뛰기" 분기 제거. QMD가 유사도로
|
|
7
6
|
// 뭐라도 물어오면 정확 문자열 매칭이 묻히는 구조라 색인이 싱싱할수록 재현율이
|
|
8
7
|
// 떨어졌다(plan.md 5.1(1)). 시맨틱은 대체재가 아니라 보완재 — 둘 다 항상 돌린다.
|
|
@@ -14,7 +13,10 @@
|
|
|
14
13
|
// - 2026-08-29 (개선계획 1-6): 수집(collect)과 렌더(render)를 분리. --json이면
|
|
15
14
|
// {schemaVersion: 1, kind: 'search-results', ...} 봉투로 출력 — 스킬 프롬프트가
|
|
16
15
|
// 사람용 출력 문자열에 깨지지 않게 하는 계약.
|
|
17
|
-
|
|
16
|
+
// - 2026-09-09 (개선계획 5-1-1): qmd 호출을 execFileSync 인자 배열로 — 검색어의
|
|
17
|
+
// 메타문자가 셸로 새는 것을 원천 차단.
|
|
18
|
+
// - 2026-09-09 (개선계획 5-1-6): findstr/grep 외부 호출을 제거하고 프로세스 안
|
|
19
|
+
// 라인 스캐너로. findstr은 ACP 의존이라 CP949 머신에서 한국어 검색어가 0건.
|
|
18
20
|
const path = require('path');
|
|
19
21
|
const fs = require('fs');
|
|
20
22
|
const { findDocRoot, loadConfig } = require('./find-doc-root');
|
|
@@ -52,7 +54,7 @@ function search(query, options = {}) {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function collectGrepResults(query, wikiPath, rawPath) {
|
|
55
|
-
// 따옴표는
|
|
57
|
+
// 따옴표는 문서 본문에 잘 안 나오는 캐럭터라 키워드에서 제외(회수율).
|
|
56
58
|
const keywords = query.split(/\s+/)
|
|
57
59
|
.map(k => k.replace(/"/g, ''))
|
|
58
60
|
.filter(k => k.length > 1);
|
|
@@ -61,34 +63,30 @@ function collectGrepResults(query, wikiPath, rawPath) {
|
|
|
61
63
|
|
|
62
64
|
const found = new Map(); // absPath → { keywords: Set, snippets: Map<lineText, true> }
|
|
63
65
|
const searchDirs = [wikiPath, rawPath];
|
|
64
|
-
const isWindows = process.platform === 'win32';
|
|
65
66
|
|
|
67
|
+
// 외부 grep/findstr을 쓰지 않는다 — findstr은 코드페이지(ACP) 의존이라 한국어
|
|
68
|
+
// Windows(CP949)에서 UTF-8 문서의 한국어 검색어가 비트 불일치로 조용히 0건이었다
|
|
69
|
+
// (2026-09-09 리뷰 5-1-6). 프로세스 안 라인 스캐너는 인코딩·플랫폼 무관하고
|
|
70
|
+
// 메타문자가 셸로 새는 경로도 원천히 없다. 코퍼스는 doc/ 규모라 속도 문제 없다.
|
|
66
71
|
for (const dir of searchDirs) {
|
|
67
72
|
if (!fs.existsSync(dir)) continue;
|
|
68
|
-
for (const
|
|
73
|
+
for (const filePath of listMarkdownFiles(dir)) {
|
|
74
|
+
let lines;
|
|
69
75
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
77
|
+
} catch { continue; }
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (!line.trim()) continue;
|
|
80
|
+
const lower = line.toLowerCase();
|
|
81
|
+
for (const kw of keywords) {
|
|
82
|
+
if (lower.includes(kw.toLowerCase())) {
|
|
83
|
+
if (!found.has(filePath)) found.set(filePath, { keywords: new Set(), snippets: new Map() });
|
|
84
|
+
const entry = found.get(filePath);
|
|
85
|
+
entry.keywords.add(kw);
|
|
86
|
+
const snippet = line.trim();
|
|
87
|
+
if (!entry.snippets.has(snippet)) entry.snippets.set(snippet, true);
|
|
88
|
+
}
|
|
78
89
|
}
|
|
79
|
-
const output = execSync(grepCmd, { encoding: 'utf8' });
|
|
80
|
-
for (const line of output.split(/\r?\n/)) {
|
|
81
|
-
if (!line.trim()) continue;
|
|
82
|
-
const { file, snippet } = splitGrepLine(line);
|
|
83
|
-
if (!file) continue;
|
|
84
|
-
const abs = path.resolve(file);
|
|
85
|
-
if (!found.has(abs)) found.set(abs, { keywords: new Set(), snippets: new Map() });
|
|
86
|
-
const entry = found.get(abs);
|
|
87
|
-
entry.keywords.add(kw);
|
|
88
|
-
if (snippet && !entry.snippets.has(snippet)) entry.snippets.set(snippet, true);
|
|
89
|
-
}
|
|
90
|
-
} catch (e) {
|
|
91
|
-
// findstr/grep returns exit code 1 if no matches
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
}
|
|
@@ -111,6 +109,19 @@ function collectGrepResults(query, wikiPath, rawPath) {
|
|
|
111
109
|
return { keywords, files };
|
|
112
110
|
}
|
|
113
111
|
|
|
112
|
+
// .md 재귀 수집 — grep -r / findstr /S 대응.
|
|
113
|
+
function listMarkdownFiles(dir) {
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const name of fs.readdirSync(dir)) {
|
|
116
|
+
const abs = path.join(dir, name);
|
|
117
|
+
let stat;
|
|
118
|
+
try { stat = fs.statSync(abs); } catch { continue; }
|
|
119
|
+
if (stat.isDirectory()) out.push(...listMarkdownFiles(abs));
|
|
120
|
+
else if (name.toLowerCase().endsWith('.md')) out.push(abs);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
114
125
|
function collectQmdResults(query, config) {
|
|
115
126
|
const qmdPath = findQmd();
|
|
116
127
|
if (!qmdPath) return { available: false, collections: [] };
|
|
@@ -120,7 +131,10 @@ function collectQmdResults(query, config) {
|
|
|
120
131
|
const results = [];
|
|
121
132
|
for (const coll of collections) {
|
|
122
133
|
try {
|
|
123
|
-
|
|
134
|
+
// 검색어를 셸 문자열에 끼워 넣지 않는다 — 인자 배열은 메타문자를 데이터로만
|
|
135
|
+
// 전달한다(따옴표·$()·백틱 포함 질의가 에러 메시지 붙여넣기의 일상이다).
|
|
136
|
+
const output = execFileSync(process.execPath, [qmdPath, 'search', query, '-c', coll],
|
|
137
|
+
{ encoding: 'utf8', timeout: 30000 });
|
|
124
138
|
if (!isQmdEmpty(output)) results.push({ collection: coll, output: output.trim() });
|
|
125
139
|
} catch (error) {
|
|
126
140
|
// collection missing or QMD error for this collection — try next
|
|
@@ -171,16 +185,4 @@ function isQmdEmpty(output) {
|
|
|
171
185
|
return /^no\s+results\s+found\.\s*$/i.test(output.trim());
|
|
172
186
|
}
|
|
173
187
|
|
|
174
|
-
// findstr/grep 출력 행에서 경로와 매칭 줄을 분리한다.
|
|
175
|
-
// findstr: <경로>.md:<내용>
|
|
176
|
-
// grep -n: <경로>.md:<줄번호>:<내용>
|
|
177
|
-
// 경계는 첫 `.md:`로 판정 — 윈도우 절대경로의 드라이브 콜론(`D:`)과 충돌을 피한다.
|
|
178
|
-
function splitGrepLine(line) {
|
|
179
|
-
const idx = line.indexOf('.md:');
|
|
180
|
-
if (idx === -1) return { file: null, snippet: null };
|
|
181
|
-
const file = line.slice(0, idx + 3);
|
|
182
|
-
const snippet = line.slice(idx + 4).replace(/^\d+:/, ''); // POSIX grep 줄번호 제거
|
|
183
|
-
return { file, snippet: snippet.trim() };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
188
|
module.exports = { search };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybird/llm-wiki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "LLM-friendly knowledge graph + kanban for AI coding agents. Raw daily logs → compiled wiki → merged semantic+grep search, and a card-per-file kanban the work-loop skill consumes unattended. Drop-in for any repo.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kybird",
|
|
@@ -36,5 +36,8 @@
|
|
|
36
36
|
"agentic-memory",
|
|
37
37
|
"claude-code",
|
|
38
38
|
"work-loop"
|
|
39
|
-
]
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "node --test test/roundtrip.test.js test/kanban-write.test.js test/kanban-pick-guards.test.js test/kanban-board-flags.test.js test/kanban-milestone.test.js test/kanban-monitor.test.js test/kanban-wait.test.js test/find-doc-root-worktree.test.js test/auto-update.test.js"
|
|
42
|
+
}
|
|
40
43
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kanban-plan
|
|
3
|
-
description: Planning loop — turn a plan into board cards the work-loop can consume. Cards are written only via the CLI; decompose with divergence guards, gate what isn't ready.
|
|
4
|
-
skill-version:
|
|
3
|
+
description: Planning loop — turn a plan into board cards the work-loop can consume. Cards are written only via the CLI; decompose with divergence guards, gate what isn't ready, group multi-card plans under a milestone.
|
|
4
|
+
skill-version: 4
|
|
5
5
|
---
|
|
6
6
|
# When to use
|
|
7
7
|
|
|
@@ -15,7 +15,7 @@ skill-version: 2
|
|
|
15
15
|
- 기획(this skill) = **분해**: a plan becomes cards.
|
|
16
16
|
- 개발(`work-loop`) = **해소**: pick → done.
|
|
17
17
|
- QA = **수렴 강제**: fake dones get reverted; `board report` shows the ratio.
|
|
18
|
-
- The person watches progress through `llm-wiki board` / `board report` / `
|
|
18
|
+
- The person watches progress through `llm-wiki board` / `board report` / `llm-wiki monitor`.
|
|
19
19
|
Plan so that this one screen is enough — 리뷰가 카드 수에 비례하면 무인의 의미가 없다.
|
|
20
20
|
|
|
21
21
|
# Before creating cards
|
|
@@ -26,6 +26,23 @@ skill-version: 2
|
|
|
26
26
|
it (`supersede`); never spawn a near-duplicate. Titles are identifiers (plan.md 3.5):
|
|
27
27
|
no numbers, no versions in titles.
|
|
28
28
|
|
|
29
|
+
# Milestones — 계획 단위 소속 (plan.md 3.8)
|
|
30
|
+
|
|
31
|
+
A plan that decomposes into **2+ cards gets one milestone card** — the board's purpose
|
|
32
|
+
axis. One-card work needs no milestone (과잉이다).
|
|
33
|
+
|
|
34
|
+
1. `card new "<계획 제목>" --kind milestone --goal "<대의 한 문장>"` — the Goal is why
|
|
35
|
+
the plan exists; the morning human reads it above the terminal stream.
|
|
36
|
+
2. Every member card carries `--milestone "<계획 제목>"` (or `card edit --milestone`
|
|
37
|
+
later — active cards only; terminal membership is history).
|
|
38
|
+
3. The milestone is never picked and never closed by hand: `done`/`supersede`/`abandon`
|
|
39
|
+
of the last member auto-completes it (review-parked milestones only report).
|
|
40
|
+
Milestone progress is **derived**, never stored — don't manage its state.
|
|
41
|
+
|
|
42
|
+
Rules: milestones stay **flat** (a milestone never belongs to another milestone — CLI
|
|
43
|
+
rejects it). Gates don't propagate — each member carries its own `--not-before`.
|
|
44
|
+
Abandoning a milestone requires its members to be terminal first (CLI enforces, lists them).
|
|
45
|
+
|
|
29
46
|
# Decomposition rules (divergence guards, plan.md 2.3)
|
|
30
47
|
|
|
31
48
|
- One card = one context = one commit. If you cannot say what the commit would be, it is
|
|
@@ -33,7 +50,7 @@ skill-version: 2
|
|
|
33
50
|
- Children must be **strictly smaller** than the parent. When children replace a parent,
|
|
34
51
|
`supersede` it — the parent dissolves, it is not marked done.
|
|
35
52
|
- Depth ≤ 3 (plan → subtask → task). Deeper than that means you are writing the work,
|
|
36
|
-
not planning it.
|
|
53
|
+
not planning it. (The milestone sits **above** this ladder as grouping, not depth.)
|
|
37
54
|
- Do not board what is not ready to start:
|
|
38
55
|
- Time condition → `card new "<t>" --not-before 2026-09-05`. `pick` skips it until then.
|
|
39
56
|
- Observational condition → create it, then `handoff <t> --question "조건: …"`. It waits
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: work-loop
|
|
3
3
|
description: Unattended kanban loop — pick a card, resolve it with objective evidence, park judgment calls and move on. Board is the only task list; converge, don't diverge.
|
|
4
|
-
skill-version:
|
|
4
|
+
skill-version: 7
|
|
5
5
|
---
|
|
6
6
|
# When to use
|
|
7
7
|
|
|
@@ -18,6 +18,14 @@ skill-version: 4
|
|
|
18
18
|
3. Never build or assume a process supervisor. The loop lives in the session; the board
|
|
19
19
|
lives in files.
|
|
20
20
|
|
|
21
|
+
# Loop start — bring the monitor up (사람의 실시간 관측)
|
|
22
|
+
|
|
23
|
+
루프를 시작할 때 `llm-wiki monitor`를 백그라운드로 띄운다 — 밤중에 사람이 들어와도
|
|
24
|
+
클레임·진행·마일스톤이 살아 있는 보드를 본다. **멱등이다**: 이미 떠 있으면 exit 0으로
|
|
25
|
+
같은 URL을 되뇌므로 그대로 쓰고, 백그라운드 실행이 안 되는 환경이면 건너뛴다 —
|
|
26
|
+
모니터는 관측이지 의존성이 아니다. 정지 규칙에서도 모니터는 끄지 않는다(아침의
|
|
27
|
+
사람이 본다) — 읽기 전용이라 남겨두는 비용이 없다.
|
|
28
|
+
|
|
21
29
|
# Loop graph
|
|
22
30
|
|
|
23
31
|
```
|
|
@@ -27,7 +35,7 @@ pick → work → 판정 ─ done / handoff / abandon / supersede
|
|
|
27
35
|
집을 카드 없음 → review(대기) 큐 점검 → 전부 대기면 질문을 모아 정지 (반스래시)
|
|
28
36
|
```
|
|
29
37
|
|
|
30
|
-
# Rules (all
|
|
38
|
+
# Rules (all seven are load-bearing)
|
|
31
39
|
|
|
32
40
|
0. **Condition-gated cards.** Some cards must not start yet:
|
|
33
41
|
- `not_before: YYYY-MM-DD` in frontmatter (future date) — `pick` skips them
|
|
@@ -61,6 +69,10 @@ pick → work → 판정 ─ done / handoff / abandon / supersede
|
|
|
61
69
|
(보고서와 함께 세션 타임랩스 영상을 남긴다 — 아침의 사람이 40초로 밤을 본다),
|
|
62
70
|
leave the questions in one place, and STOP. Do not invent new cards to look
|
|
63
71
|
productive. 밤새 카드가 300장이 되는 것이 이 시스템이 죽는 방식이다 (plan.md 2.3).
|
|
72
|
+
Milestone cards are **never pickable** (they are grouping, not work — plan.md 3.8):
|
|
73
|
+
if the No-pickable detail lists them, that is normal — don't `done` or `abandon` a
|
|
74
|
+
milestone by hand; it auto-completes when its last member terminates (a `done` that
|
|
75
|
+
prints `◉ milestone 완료` closed it for you).
|
|
64
76
|
|
|
65
77
|
5. **Search before work.** `llm-wiki search "<keywords>"` before starting a card. If the
|
|
66
78
|
wall you are about to hit already has an abandoned card or an anti-pattern page, skip
|
|
@@ -70,6 +82,14 @@ pick → work → 판정 ─ done / handoff / abandon / supersede
|
|
|
70
82
|
On a long card, `llm-wiki card edit <제목> --renew-claim` before the timeout, or
|
|
71
83
|
another loop instance will reclaim the card under you.
|
|
72
84
|
|
|
85
|
+
7. **Worktrees share one board.** The board is a project resource, not a branch resource:
|
|
86
|
+
from a linked worktree, every `pick`/`done`/`card` writes the **primary worktree's**
|
|
87
|
+
`doc/kanban/`. If this loop runs in a linked worktree, its card changes appear as
|
|
88
|
+
uncommitted changes in the primary worktree — leave them there (the primary's next
|
|
89
|
+
commit picks them up); do not chase them into this worktree's commits, and do not
|
|
90
|
+
commit in the primary from here. `LLM_WIKI_WORKTREE_LOCAL=1` restores per-worktree
|
|
91
|
+
boards.
|
|
92
|
+
|
|
73
93
|
# Splitting (supersede) — divergence guard
|
|
74
94
|
|
|
75
95
|
Supersede only when each child is **strictly smaller** than the parent in context needed.
|
package/templates/AGENTS.md
CHANGED
|
@@ -13,12 +13,17 @@ Files are the source of truth. Kanban card files are written ONLY via the CLI
|
|
|
13
13
|
| After fixing a bug / making a decision / discovering something | `wiki-log` skill → a Case in `doc/raw/YYYY-MM-DD.md` with verbatim error + `hash:` grounding |
|
|
14
14
|
| When raw logs have accumulated | `wiki-compile` skill → promote to `doc/wiki/` pages, then `llm-wiki compile index` |
|
|
15
15
|
| Sanity check of the knowledge base | `wiki-lint` skill or `llm-wiki lint` |
|
|
16
|
-
| Planning work (person present) | `kanban-plan` skill → cards via `llm-wiki card new "<title>"` |
|
|
16
|
+
| Planning work (person present) | `kanban-plan` skill → cards via `llm-wiki card new "<title>"`. A plan that splits into 2+ cards gets **one milestone card** (`--kind milestone`, members carry `--milestone`); milestones are never picked and auto-complete when all members terminate |
|
|
17
17
|
| Unattended execution | `work-loop` skill → `llm-wiki pick --claim <name>`, park judgment calls with `handoff` |
|
|
18
|
+
| Watching the board (human) | `llm-wiki monitor` — read-only live view (claims, elapsed, gates, terminal pile, milestones) at `http://127.0.0.1:4747` |
|
|
18
19
|
|
|
19
20
|
## Rules
|
|
20
21
|
|
|
21
22
|
- Quote error messages **character-for-character** in logs and wiki pages — `llm-wiki lint`
|
|
22
23
|
back-checks every quote and `hash:` against `doc/raw/`.
|
|
23
24
|
- Follow `status: deprecated` → `superseded_by` when reading wiki pages.
|
|
25
|
+
- The board is a **project** resource, not a branch resource: from any git linked worktree,
|
|
26
|
+
`llm-wiki` reads and writes the **primary worktree's** `doc/kanban/`. `pick`/`done` run in
|
|
27
|
+
a secondary worktree leave uncommitted changes in the primary worktree — intended, commit
|
|
28
|
+
them there. Opt out with `LLM_WIKI_WORKTREE_LOCAL=1`.
|
|
24
29
|
- Expand this file with this repo's own conventions. Keep it short — it loads every session.
|