klyro 0.1.14 → 0.1.16
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/READ.md +467 -0
- package/dist/agent/runtime.js +1 -1
- package/dist/cli/repl.js +4 -11
- package/dist/tui/app.js +1 -1
- package/package.json +2 -1
package/READ.md
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
# Klyro — Complete Build Documentation
|
|
2
|
+
|
|
3
|
+
**For any coding agent:** This file is the single source of truth for what has been built till now (v0.1.15, Levels 1-5 complete, Level 6-8 partial, TUI full-screen). After reading, you have the complete picture.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. What is Klyro?
|
|
8
|
+
|
|
9
|
+
**Klyro** is an autonomous AI coding harness — a terminal-native agent that understands repositories, executes tools, verifies its work, and repairs failures.
|
|
10
|
+
|
|
11
|
+
> **Vision:** *Build a highly autonomous software engineering harness capable of understanding complex codebases, executing long-running tasks, dynamically coordinating workflows, verifying its own work, and continuously improving through evaluation.* — `PRD.md:3`
|
|
12
|
+
|
|
13
|
+
**Not a chatbot.** The harness wraps the model: `CLI → Session → Context → Agent Runtime → Model → Tool → Observation → Verification → Repair → Persistence`.
|
|
14
|
+
|
|
15
|
+
**Current version:** `0.1.15` (`package.json:3`, `npm view klyro dist-tags → latest:0.1.15`, `43 files 309/309 tests`)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2. Tech Stack
|
|
20
|
+
|
|
21
|
+
| Layer | Choice | Why |
|
|
22
|
+
|-------|--------|-----|
|
|
23
|
+
| Language | TypeScript 5.5, Node 20+ | Strict, `NodeNext`, `tsc` → `dist/` |
|
|
24
|
+
| CLI | `commander 12.1` | Stable, `InvalidArgumentError` for `exit 2` |
|
|
25
|
+
| TUI | `ink 7.1` + `react 19` + `ink-spinner 5` | React for terminal, proven by Claude Code/Gemini |
|
|
26
|
+
| Schema | `zod 4.5` | Tool input validation + config schema |
|
|
27
|
+
| Test | `vitest 4.1` `fileParallelism:false` `10s timeout` | `node` env, deterministic mocks |
|
|
28
|
+
| Build | `tsc` (not `tsup`) | `tsc --noEmit` `typecheck`, `tsc` `build` |
|
|
29
|
+
| Providers | Native `fetch` (Node 20) | No SDK lock-in, 3 adapters |
|
|
30
|
+
| Workspace | `pnpm-workspace.yaml` `packages/*` | `shared` `KlyroError` |
|
|
31
|
+
| CI | `.github/workflows/ci.yml` `ubuntu/macos/windows × 20/22` | `pnpm install` `typecheck` `test` `build` `pack` |
|
|
32
|
+
|
|
33
|
+
**No Docker, no MCP, no browser in MVP** — deferred to post-1.0.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 3. Architecture (Full)
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
USER
|
|
41
|
+
│
|
|
42
|
+
▼
|
|
43
|
+
CLI / TUI (ink) ←→ Global flags (--cwd/--config/--debug/--json) + completion + update + login
|
|
44
|
+
│
|
|
45
|
+
▼
|
|
46
|
+
Session Manager (SessionStore + TraceWriter + AuditLog)
|
|
47
|
+
│
|
|
48
|
+
▼
|
|
49
|
+
Task Analyzer
|
|
50
|
+
│
|
|
51
|
+
▼
|
|
52
|
+
Context Engine (L6 ProjectMap + L7 Telemetry + L8 Accounting)
|
|
53
|
+
│
|
|
54
|
+
▼
|
|
55
|
+
Agent Runtime (ProviderAdapter → Policy → Registry → Tool → Observation → Repair)
|
|
56
|
+
│
|
|
57
|
+
├─ Model Layer (OpenAI httpChatAdapter / Anthropic anthropicAdapter / retryingAdapter)
|
|
58
|
+
├─ Tool Registry (18 tools: fs/search/shell/git/verify/plan)
|
|
59
|
+
├─ Policy Engine (modes default|plan|accept-edits|auto + .env guard)
|
|
60
|
+
├─ Memory Engine (history 40 turns / 80k chars, pair-preserving)
|
|
61
|
+
└─ Verification Engine (verify + detect + auto + repair loop ≤3)
|
|
62
|
+
│
|
|
63
|
+
▼
|
|
64
|
+
Repair Loop (verify → classify → diagnosis → repair → re-verify)
|
|
65
|
+
│
|
|
66
|
+
▼
|
|
67
|
+
Completion Engine (diff + report + status)
|
|
68
|
+
│
|
|
69
|
+
▼
|
|
70
|
+
Observability ( Ink transcript + status line + trace JSONL)
|
|
71
|
+
│
|
|
72
|
+
▼
|
|
73
|
+
Evals (harness + fixtures + compare)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Data Flow (one `klyro run`):**
|
|
77
|
+
```
|
|
78
|
+
Task → makeRunSystemPrompt() injects L6 (12k) + KLYRO.md (4k) + telemetry
|
|
79
|
+
→ CallRequest{system, transcript, tools} → ProviderAdapter.stream()
|
|
80
|
+
→ StreamEvent{ text_delta | tool_call_start/delta/end | message_end | error }
|
|
81
|
+
→ finalize ToolUseBlock → PolicyEngine.evaluate() → ApprovalPrompt → registry.execute()
|
|
82
|
+
→ redactOutput() deep → transcript.push(tool_result) → telemetry.record*()
|
|
83
|
+
→ hasEdits? → verify() → detect() → diagnosticForModel() → repair (up to 3)
|
|
84
|
+
→ checkpoint (SessionStore + TraceWriter) per step
|
|
85
|
+
→ onEvent → stdout/stderr (human) | JSONL | TUI
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 4. 20-Level Plan — Status
|
|
91
|
+
|
|
92
|
+
| Level | Title | Spec `plan.md` | Status | Key Files |
|
|
93
|
+
|-------|-------|----------------|--------|-----------|
|
|
94
|
+
| **1** | Professional CLI Foundation | `1.1` Skeleton `1.2` Parser `1.3` Config `1.4` REPL `1.5` Output | **5/5 PASS** | `src/index.ts:50` `src/cli/config.ts:10` `src/cli/doctor.ts:1` `src/tui/app.tsx:14` |
|
|
95
|
+
| **2** | AI Chat Core | `2.1` Provider `2.2` OpenAI `2.3` Messages `2.4` Streaming `2.5` Cost/headless | **5/5 PASS** | `src/providers/model-info.ts:1` `src/cli/auth.ts:1` `src/context/system-prompt.ts:1` `src/tui/status.tsx:30` |
|
|
96
|
+
| **3** | Tool Runtime | `3.1` Events `3.2` read/write `3.3` shell `3.4` Permissions `3.5` Loop | **5/5 PASS** | `src/events/catalog.ts:1` `src/tools/fs/read-file.ts:39` `src/tools/shell/shell-exec.ts:20` `src/policy/engine.ts:34` `src/agent/runtime.ts:375` |
|
|
97
|
+
| **4** | Repository Coding Agent | `4.1` Edit core `4.2` Fuzzy `4.3` Navigation `4.4` KLYRO.md `4.5` Checkpoints | **5/5 PASS** | `src/tools/fs/edit-file.ts:26` `src/tools/fs/multi-edit.ts:1` `src/context/klyro-md.ts:1` `src/checkpoints/store.ts:1` |
|
|
98
|
+
| **5** | Autonomous Task Loop + Eval | `5.1` Limits `5.2` Stuck `5.3` Plan `5.4` Harness `5.5` Suite | **5/5 PASS** | `src/agent/runtime.ts:152` `phases` `src/tools/plan/todo-write.ts:1` `evals/fixtures/*` `evals/results/baseline.json` |
|
|
99
|
+
| **6** | Verification + Repair | `6.1` Verifiers `6.2` Parsers `6.3` Scoped `6.4` Repair `6.5` Contract | **PARTIAL** (engine exists, but harness needs `check.sh` from 5.5) | `src/verification/engine.ts:28` `auto.ts:10` |
|
|
100
|
+
| **7** | Codebase Intelligence | `7.1` Scanner `7.2` RepoMap `7.3` Search `7.4` Symbols | **PARTIAL** (heuristic, no tree-sitter) | `src/context/project-map.ts:352` `repo-map.ts:33` |
|
|
101
|
+
| **8** | Context Intelligence | `8.1` Accounting `8.2` Lifecycle `8.3` Compaction | **STUB** (tokenizer exists, not wired) | `src/context/tokenizer.ts:35` |
|
|
102
|
+
| **9** | Sessions, Persistence | `9.1` Store `9.2` Continue | **PARTIAL** (JSON store, no SQLite) | `src/persistence/store.ts:52` `session.ts:10` |
|
|
103
|
+
| **10** | Professional Harness | MCP, Hooks, SDK | **NOT STARTED** | — |
|
|
104
|
+
| **11-20** | Multi-Agent → Adaptive Platform | | **NOT STARTED** | — |
|
|
105
|
+
|
|
106
|
+
**Graduation for L1-L5 — PASS** (verified `vitest 309/309`, `klyro --version --help config list doctor -p "hi"`, `20-turn`, `cancel <1s`, `10/10 smoke` `evals/fixtures`).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 5. Detailed Flows (Per Level)
|
|
111
|
+
|
|
112
|
+
### L1 — CLI
|
|
113
|
+
```
|
|
114
|
+
klyro --version → readVersion() tries 3 candidates src/index.ts:28 → 0.1.15
|
|
115
|
+
klyro --help → commander tree src/index.ts:50 + showSuggestionAfterError
|
|
116
|
+
klyro → isTTY ? startRepl() : repl() src/index.ts:84 + --tui/--chat + -p headless src/index.ts:60
|
|
117
|
+
klyro config → 5-layer loadMergedConfig() src/cli/config.ts:80 → Zod validate → JSONC stripJsonComments()
|
|
118
|
+
klyro doctor → 7 checks src/cli/doctor.ts:20 (Node/config/Provider/Sessions/git/Tools/Platform)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### L2 — AI Chat
|
|
122
|
+
```
|
|
123
|
+
Provider resolution: flags → env KLYRO_PROVIDER → inferProviderFromBaseURL() src/agent/registry.ts:40 (9router→openai alias) → httpChatAdapter / anthropicAdapter
|
|
124
|
+
Streaming: httpChatAdapter.stream() src/agent/provider-adapter.ts:202 → fetch POST /chat/completions stream:true → SSE \n\n → toolIds map + pendingUsage + redacted error
|
|
125
|
+
Retry: retryingAdapter src/agent/retry.ts:46 streamWithAbort() + it.return() + combined signal (req.signal + opts.signal)
|
|
126
|
+
Cost: getModelInfo() src/providers/model-info.ts:1 estimateCost() → StatusLine src/tui/status.tsx:30 $0.043 · ctx 6%
|
|
127
|
+
Headless: klyro -p "hi" argument('[prompt]') src/index.ts:60 → runOnce output json|stream-json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### L3 — Tool Runtime
|
|
131
|
+
```
|
|
132
|
+
KlyroEvent bus src/events/bus.ts:1 globalBus.emit() + TraceWriter src/trace/writer.ts:1 JSONL fsync on tool.result
|
|
133
|
+
Tool<TInput,TOutput> src/tools/types.ts:1 {name, description, inputSchema, permission, isConcurrencySafe, renderCall}
|
|
134
|
+
read_file: 2000 window src/tools/fs/read-file.ts:70, 10MB refusal src/tools/fs/read-file.ts:49, binary null-byte src/tools/fs/read-file.ts:60, 8k tokens hint
|
|
135
|
+
write_file: wasRead guard src/tools/fs/read-history.ts:1, diff, 0600, atomic tmp+fsync
|
|
136
|
+
shell: 120s max 600s src/tools/shell/shell-exec.ts:20, persistentCwd, filteredEnv(), 30k head+tail → ~/.klyro/tool-output/<id>.txt, tree-kill taskkill/SIGKILL, interactive block
|
|
137
|
+
Policy: mode default|plan|accept-edits|auto src/policy/engine.ts:34, glob allow/deny/ask matchesGlobRule() src/policy/engine.ts:120, .env deny src/policy/engine.ts:99, sandbox --add-dir
|
|
138
|
+
Loop: stream→collect → no tools? return → policy → parallel if all isConcurrencySafe src/agent/runtime.ts:386 → Promise.all else sequential
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### L4 — Repository Coding
|
|
142
|
+
```
|
|
143
|
+
edit_file: EOL CRLF/LF src/tools/fs/edit-file.ts:40, BOM src/tools/fs/edit-file.ts:40, trailing newline, staleness mtime+hash src/tools/fs/edit-file.ts:26, actionable findClosestMatch() src/tools/fs/edit-file.ts:70
|
|
144
|
+
Fuzzy tiers after exact fails: trailing-whitespace → indent → unicode quotes → line-window 0.95 src/tools/fs/edit-file.ts:40
|
|
145
|
+
multi_edit atomic src/tools/fs/multi-edit.ts:10, apply_patch Codex tolerant src/tools/fs/apply-patch.ts:1
|
|
146
|
+
Navigation: list_dir/glob/grep respect .klyroignore, git_log/status/diff src/tools/git/git-log.ts:1, background shell src/tools/shell/background.ts:1 1MB ring
|
|
147
|
+
KLYRO.md: loadKlyroMd() ~/.klyro/KLYRO.md → KLYRO.local.md → subdir lazy src/context/klyro-md.ts:1 @import depth5
|
|
148
|
+
Checkpoints: snapshot() src/checkpoints/store.ts:13 .klyro/checkpoints/<id> → diff vs HEAD → undo/rewind
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### L5 — Autonomous Loop + Eval
|
|
152
|
+
```
|
|
153
|
+
Loop controller: maxSteps alias maxTurns src/agent/runtime.ts:152, maxCost/maxTimeMs src/agent/runtime.ts:180 → status:limit phase:limit exit 7
|
|
154
|
+
Phases: understanding→exploring→planning→implementing→verifying setPhase() src/agent/runtime.ts:180 phase.changed
|
|
155
|
+
Stuck: identical call×3 src/agent/runtime.ts:180, same file >8× fileEditCounts, ≥5 fails → [system note]
|
|
156
|
+
Planning: todo_write src/tools/plan/todo-write.ts:1 → .klyro/plans/todos.json, plan mode blocks writes, ask_user src/tools/plan/ask-user.ts:1 HEADLESS → KLYRO_AUTO_ANSWER
|
|
157
|
+
Eval: FileFixture {dir, task.md, check.sh, meta.json} loadFileFixture() src/eval/harness.ts:130, runFileFixture() tmp cp + bash -c check.sh, harness 10 fixtures evals/fixtures/*, baseline evals/results/baseline.json 8/10, compareReports() src/eval/harness.ts:180, klyro eval --suite smoke src/cli/eval.ts:84
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 6. File Structure (Purpose)
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
klyro/
|
|
166
|
+
├── package.json # klyro 0.1.15, bin klyro/ky, files [dist], commander/ink/zod
|
|
167
|
+
├── pnpm-workspace.yaml # packages/*
|
|
168
|
+
├── tsconfig.json # ES2022, NodeNext, strict, noUncheckedIndexedAccess
|
|
169
|
+
├── vitest.config.ts # include src/**/*.test, fileParallelism:false
|
|
170
|
+
├── .github/workflows/ci.yml # ubuntu/macos/windows × 20/22 → typecheck/test/build/eval smoke
|
|
171
|
+
├── src/
|
|
172
|
+
│ ├── index.ts # commander entry: tui/run/chat/eval/session/resume + global flags --cwd/--config/-p
|
|
173
|
+
│ ├── chat.ts # one-shot chat, normalizeBaseURL, assertSafeBaseURL (loopback+private+ALLOW_INSECURE), streamToStdout \n\n, writeWithBackpressure, readBoundedText
|
|
174
|
+
│ ├── repl.ts # legacy readline REPL, history 40/80k, trimHistory pair-preserving
|
|
175
|
+
│ ├── providers.ts # resolveProvider() probe Ollama 11434 etc 600ms, providerHelp()
|
|
176
|
+
│ ├── agent/
|
|
177
|
+
│ │ ├── runtime.ts # run() loop: CallRequest → ProviderAdapter → policy → registry → checkpoint → verify→repair (≤3) → trace
|
|
178
|
+
│ │ ├── message.ts # Message{role, content: TextBlock|ToolUseBlock|ToolResultBlock}
|
|
179
|
+
│ │ ├── provider-adapter.ts # httpChatAdapter, buildChatCompletionsBody, streamChatCompletions \n\n + pendingUsage + redact + isConcurrencySafe
|
|
180
|
+
│ │ ├── anthropic-adapter.ts # anthropicAdapter, x-api-key, indexToToolId map, assertSafeBaseURL
|
|
181
|
+
│ │ ├── registry.ts # buildProvider(), inferProviderFromBaseURL() 9router→openai, isLoopback, retryingAdapter
|
|
182
|
+
│ │ ├── retry.ts # retryingAdapter, computeBackoff, streamWithAbort it.return()
|
|
183
|
+
│ │ ├── worker-spawner.ts # stub for future sub-agents
|
|
184
|
+
│ │ └── observation.ts # ObservationStore (placeholder)
|
|
185
|
+
│ ├── tools/
|
|
186
|
+
│ │ ├── types.ts # Tool<TIn,TOut> + ToolContext{sessionId,permissions,logger,emit}
|
|
187
|
+
│ │ ├── registry.ts # ToolRegistry 18 tools: fs/search/shell/git/verify/plan
|
|
188
|
+
│ │ ├── schema.ts # zodToJsonSchema (Zod3/4)
|
|
189
|
+
│ │ ├── normalize.ts # safe(), TOOL_ERROR_CODES
|
|
190
|
+
│ │ ├── fs/read-file.ts # 2000 window, 10MB, binary, 8k hint, wasRead
|
|
191
|
+
│ │ ├── fs/write-file.ts # 0600 tmp+fsync, diff, needsApproval
|
|
192
|
+
│ │ ├── fs/edit-file.ts # EOL/BOM/trailing, staleness, findClosestMatch, fuzzy tiers
|
|
193
|
+
│ │ ├── fs/multi-edit.ts # atomic sequential
|
|
194
|
+
│ │ ├── fs/apply-patch.ts # Codex tolerant
|
|
195
|
+
│ │ ├── fs/read-history.ts # wasRead Set
|
|
196
|
+
│ │ ├── fs/list-dir.ts # depth, .klyroignore
|
|
197
|
+
│ │ ├── search/glob.ts # globToRegex **, SKIP_DIRS
|
|
198
|
+
│ │ ├── search/grep.ts # RegExp g + lastIndex, binary skip, 500 cap, ReDoS risk noted
|
|
199
|
+
│ │ ├── search/search-files.ts# fzf query, firstParty+recency ranking
|
|
200
|
+
│ │ ├── search/recent-files.ts# mtime >= since, .map skip
|
|
201
|
+
│ │ ├── search/dependencies.ts# npm/py/go/rust/Cargo parsers
|
|
202
|
+
│ │ ├── shell/shell-exec.ts # 120s, persistentCwd, filteredEnv, 30k head+tail, tool-output file, DANGEROUS_PATTERNS + $HOME
|
|
203
|
+
│ │ ├── shell/background.ts # 1MB ring, startBackground/getOutput/killJob
|
|
204
|
+
│ │ ├── git/git-status.ts # porcelain + branch + log
|
|
205
|
+
│ │ ├── git/git-log.ts # read-only
|
|
206
|
+
│ │ └── verify/run-verify.ts # 5m, 256k cap
|
|
207
|
+
│ ├── context/
|
|
208
|
+
│ │ ├── repo-map.ts # regex symbols ts/js/py/go/rs, 500 files
|
|
209
|
+
│ │ ├── project-map.ts # language/framework/PM/test detection
|
|
210
|
+
│ │ ├── level6.ts # buildLevel6Context 12k = project+repo(40)+recent(15)+deps
|
|
211
|
+
│ │ ├── level7.ts # RuntimeTelemetry step/tools/errors/tokens
|
|
212
|
+
│ │ ├── tokenizer.ts # estimateTokens chars/4, compressTranscript deep-copy
|
|
213
|
+
│ │ ├── selector.ts # selectFiles (dead, not wired)
|
|
214
|
+
│ │ ├── snippets.ts # readSnippet windowed
|
|
215
|
+
│ │ └── system-prompt.ts # buildSystemPrompt layered identity→env→global
|
|
216
|
+
│ ├── policy/
|
|
217
|
+
│ │ ├── engine.ts # PolicyEngine 4 rules + mode/ glob/.env/add-dir
|
|
218
|
+
│ │ ├── approval.ts # StdinApprovalPrompt/DenyAll/InMemoryAllowlist + TuiApprovalBridge
|
|
219
|
+
│ │ ├── path-guard.ts # resolveWithinCwd + resolveAndFollowSymlinks (TOCTOU noted)
|
|
220
|
+
│ │ └── secret-redactor.ts # redact() 7 patterns + createRedactor 1KiB tail
|
|
221
|
+
│ ├── persistence/
|
|
222
|
+
│ │ ├── store.ts # SessionStore JSON per session + sessions.json index (atomic tmp→fsync→rename, retry)
|
|
223
|
+
│ │ ├── session.ts # getDefaultSessionsDir ~/.klyro/sessions, formatSession, resolveSessionId prefix
|
|
224
|
+
│ │ └── audit.ts # AuditLog JSONL
|
|
225
|
+
│ ├── verification/
|
|
226
|
+
│ │ ├── engine.ts # verify() spawn shell, timeout SIGKILL, detect(), diagnosticForModel()
|
|
227
|
+
│ │ ├── detect.ts # heuristic type/test/lint/build/runtime + summarize()
|
|
228
|
+
│ │ └── auto.ts # detectVerifyCommand() npm test / tsc / make
|
|
229
|
+
│ ├── checkpoints/
|
|
230
|
+
│ │ └── store.ts # snapshot/.klyro/checkpoints/<id> diff/undo/rewind
|
|
231
|
+
│ ├── events/
|
|
232
|
+
│ │ ├── catalog.ts # KlyroEvent 15 types
|
|
233
|
+
│ │ └── bus.ts # EventBus + globalBus
|
|
234
|
+
│ ├── trace/
|
|
235
|
+
│ │ └── writer.ts # TraceWriter JSONL appendFile + fsync on tool.result
|
|
236
|
+
│ ├── renderers/
|
|
237
|
+
│ │ ├── terminal.ts # TerminalRenderer
|
|
238
|
+
│ │ └── json.ts # JsonRenderer
|
|
239
|
+
│ ├── tui/
|
|
240
|
+
│ │ ├── app.tsx # Ink full-screen: Static history + live region batched 30fps + single useInput
|
|
241
|
+
│ │ ├── tokens.ts # accent #8B7CF6, glyphs › ● ⎿ ✔, spacing
|
|
242
|
+
│ │ ├── banner.tsx # 5.1 Banner + resume
|
|
243
|
+
│ │ ├── input-box.tsx # 5.2 InputBox queued
|
|
244
|
+
│ │ ├── thinking-block.tsx # 5.5 Thinking
|
|
245
|
+
│ │ ├── activity-line.tsx # 5.6 Spinner + verb·elapsed
|
|
246
|
+
│ │ ├── transcript.tsx # 11 kinds: text/tool/policy/error/file_changed/diff
|
|
247
|
+
│ │ ├── status.tsx # model step/max repairs tokens cost ctx%
|
|
248
|
+
│ │ ├── header.tsx # abbrevPath
|
|
249
|
+
│ │ ├── diff.tsx # DiffView per-file hunk
|
|
250
|
+
│ │ ├── approval.tsx # TuiApprovalBridge y/a/A/n/e/?
|
|
251
|
+
│ │ ├── plan.tsx # PlanView glyphs ◯●✓✗⊘
|
|
252
|
+
│ │ └── diff-parser.ts # parseUnifiedDiff
|
|
253
|
+
│ ├── cli/
|
|
254
|
+
│ │ ├── repl.ts # startRepl() Ink TUI: resolveProvider probe, httpChat/anthropic, queued bridge, verify repair loop
|
|
255
|
+
│ │ ├── run.ts # runOnce() one-shot: verify/persist, session create/resume, headless -p
|
|
256
|
+
│ │ ├── eval.ts # runEval() JSONL suite + --suite smoke file fixtures
|
|
257
|
+
│ │ ├── config.ts # 5-layer loadMergedConfig, Zod, JSONC stripJsonComments, get|set|list|path
|
|
258
|
+
│ │ ├── doctor.ts # 7 checks Node/config/Provider/Sessions/git/Tools/Platform
|
|
259
|
+
│ │ ├── completion.ts # bash|zsh|fish|powershell
|
|
260
|
+
│ │ ├── update.ts # 24h cache, KLYRO_NO_UPDATE_CHECK
|
|
261
|
+
│ │ ├── auth.ts # login 0600 credentials.json, MODEL_ALIASES sonnet→claude
|
|
262
|
+
│ │ ├── markdown.ts # renderMarkdown incremental, stripMarkdown
|
|
263
|
+
│ │ ├── errors.ts # handleFatal ✖/hint, setupGlobalHandlers
|
|
264
|
+
│ │ └── slash/parser.ts # /clear/compact/model/diff/plan/status/quit/help/config/doctor/cost/thinking
|
|
265
|
+
│ ├── shared/
|
|
266
|
+
│ │ ├── errors.ts # KlyroError{code,exitCode}
|
|
267
|
+
│ │ └── types.ts # Role, Message, ExitCode
|
|
268
|
+
│ └── util/log.ts # pino JSON ~/.klyro/logs/klyro-YYYY-MM-DD.log 14d rotation redact
|
|
269
|
+
├── packages/shared # workspace: @klyro/shared (re-export)
|
|
270
|
+
├── evals/
|
|
271
|
+
│ ├── fixtures/ # 10 smoke fixtures: read-answer, add-fn-test, fix-failing-test…
|
|
272
|
+
│ └── results/baseline.json # 8/10 80% + markdown compareReports()
|
|
273
|
+
├── .klyro/ # runtime: sessions.json, traces/*.jsonl, checkpoints/<id>, logs/, history, memory/
|
|
274
|
+
└── dist/ # tsc output, bin klyro
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 7. CLI Reference
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
klyro # TUI REPL (TTY) or legacy pipe REPL
|
|
283
|
+
klyro --tui / --no-tui # force TUI / legacy
|
|
284
|
+
klyro -p "prompt" # headless one-shot (stdin piped + --output-format json)
|
|
285
|
+
klyro tui [-m model] [--max-steps n]
|
|
286
|
+
klyro run <prompt> [-m model] [--max-steps 30] [--max-cost $] [--max-time ms]
|
|
287
|
+
[--verify/--no-verify --verify-command <cmd> --max-repairs 3]
|
|
288
|
+
[--persist/--no-persist --resume-session <id>] [--output human|json|silent]
|
|
289
|
+
klyro chat [prompt] [-s system] # legacy streamed chat
|
|
290
|
+
klyro eval <input.jsonl> | --suite smoke [--filter str] [--runs 1] [--parallel 1] [--output json]
|
|
291
|
+
klyro eval:compare <a.json> <b.json>
|
|
292
|
+
klyro config [list|get <key>|set <key> <value>|unset <key>|path|edit]
|
|
293
|
+
klyro doctor [--json]
|
|
294
|
+
klyro completion <bash|zsh|fish|powershell>
|
|
295
|
+
klyro update
|
|
296
|
+
klyro login / logout [provider] # 0600 credentials.json, MODEL_ALIASES
|
|
297
|
+
klyro session list|show <id>|resume <id> [--json]
|
|
298
|
+
klyro resume <id> # alias
|
|
299
|
+
klyro trace <id> [--stats --json]
|
|
300
|
+
klyro --version / --help
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Global flags (preAction `src/index.ts:60`): `--cwd <path>` `--config <path>` `--debug` `--verbose` `--quiet` `--json` `--yes` `--no-color` (respects `NO_COLOR/FORCE_COLOR`)
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 8. Provider System
|
|
308
|
+
|
|
309
|
+
- **Resolution:** `flags` → `KLYRO_PROVIDER` (aliases `9router/openrouter/groq→openai`) → `inferProviderFromBaseURL()` `anthropic.com` exact → `openai` default `src/agent/registry.ts:30`
|
|
310
|
+
- **Adapters:** `httpChatAdapter` `src/agent/provider-adapter.ts:190` (`/chat/completions` SSE `\n\n`, `toolIds` map + `pendingUsage`, `redact` error) + `anthropicAdapter` `src/agent/anthropic-adapter.ts:76` (`/v1/messages` `x-api-key`, `indexToToolId` `Map<number,string>` `src/agent/anthropic-adapter.ts:169` fixes `findToolIdByIndex` interleaving) + `retryingAdapter` `src/agent/retry.ts:46` `computeBackoff` `base*2^attempt ±25%` `3 attempts 500ms/8s`, `streamWithAbort` `it.return()` `src/agent/retry.ts:46`
|
|
311
|
+
- **Security:** `assertSafeBaseURL()` `src/chat.ts:38` `https:` OK, `http:` only `localhost/127.*` `10/8` `192.168/16` `172.16/12` or `KLYRO_ALLOW_INSECURE=1` with warning.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 9. Tool System (18 tools)
|
|
316
|
+
|
|
317
|
+
`ToolRegistry` `src/tools/registry.ts:81` `register/get/list/toOpenAITools/execute` (`safeParse` → `INVALID_INPUT`).
|
|
318
|
+
|
|
319
|
+
| Tool | Input | Output | Notes |
|
|
320
|
+
|------|-------|--------|-------|
|
|
321
|
+
| `read_file` | `path, startLine?, endLine?, maxBytes?` | `lines, totalLines, bytesRead, truncated` | `cat -n` 2000 window, 10MB `src/tools/fs/read-file.ts:49`, binary `null-byte` `src/tools/fs/read-file.ts:60`, `wasRead` `src/tools/fs/read-history.ts:1` |
|
|
322
|
+
| `write_file` | `path, content` | `bytesWritten, diff` | `0600` tmp `crypto.randomBytes` + `fsync` + `rename` `src/tools/fs/write-file.ts:36`, `wasRead` guard `>200` lines `needsApproval` |
|
|
323
|
+
| `edit_file` | `path, find, replace, replaceAll?` | `replacements, diff` | `EOL/BOM/trailing` `src/tools/fs/edit-file.ts:40`, staleness `mtime+hash` `src/tools/fs/edit-file.ts:26`, `findClosestMatch` `src/tools/fs/edit-file.ts:70`, fuzzy 4 tiers `src/tools/fs/edit-file.ts:40` |
|
|
324
|
+
| `multi_edit` | `path, edits[{find,replace}]` | `edits, diff` | atomic `src/tools/fs/multi-edit.ts:10` |
|
|
325
|
+
| `apply_patch` | `patch` | `patchedFiles` | Codex tolerant `src/tools/fs/apply-patch.ts:1` |
|
|
326
|
+
| `list_directory` | `path, maxDepth?` | `entries` | skips `DEFAULT_SKIP` + dotfiles `src/tools/fs/list-dir.ts:67`, depth `maxDepth` |
|
|
327
|
+
| `glob` | `pattern, cwd?` | `paths` | `globToRegex` `**/` `src/tools/search/glob.ts:50` |
|
|
328
|
+
| `grep` | `pattern, path?, include?` | `matches` | `RegExp g` `lastIndex=0` `src/tools/search/grep.ts:49` `500` cap, binary skip |
|
|
329
|
+
| `search_files` | `query` | `paths` | ranking `exact basename 200` `src/tools/search/search-files.ts:1` |
|
|
330
|
+
| `recent_files` | `sinceHours?, glob?` | `paths` | `mtime >= sinceMs` `src/tools/search/recent-files.ts:1` |
|
|
331
|
+
| `dependencies` | `manager?` | `deps` | `npm/py/go/rust` parsers `src/tools/search/dependencies.ts:1` |
|
|
332
|
+
| `shell_exec` | `command, cwd?, timeoutMs?, env?` | `exitCode, stdout, truncated` | `120s max 600s` `src/tools/shell/shell-exec.ts:20` `persistentCwd` `filteredEnv()` `30k head+tail` `~/.klyro/tool-output/<id>.txt` `taskkill /F /T` `src/tools/shell/shell-exec.ts:182`, `DANGEROUS_PATTERNS` `rm -rf` `curl|sh` `src/tools/shell/shell-exec.ts:30` |
|
|
333
|
+
| `git_status` | — | `porcelain, branch, log` | `spawn git` `shell:false` `15s` `src/tools/git/git-status.ts:1` |
|
|
334
|
+
| `git_diff` | `cached?` | `diff, stat, patchedFiles` | `diff --git` regex `src/tools/git/git-diff.ts:61` |
|
|
335
|
+
| `git_log` | `limit?, path?` | `log` | read-only `src/tools/git/git-log.ts:1` |
|
|
336
|
+
| `run_verify` | `command` | `ok, exitCode` | `5m 256k` `src/tools/verify/run-verify.ts:1` `taskkill` |
|
|
337
|
+
| `todo_write` | `todos[{id,title,status}]` | `updated` | `src/tools/plan/todo-write.ts:1` `.klyro/plans/todos.json` |
|
|
338
|
+
| `ask_user` | `question, options?` | `answer` | `src/tools/plan/ask-user.ts:1` `HEADLESS` `KLYRO_AUTO_ANSWER` |
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 10. Context Engine
|
|
343
|
+
|
|
344
|
+
- **L6 static:** `buildLevel6Context()` `src/context/level6.ts:131` `12k` = `projectMap` `src/context/project-map.ts:352` (`language/framework/PM` `statSync`) + `repoMap` `src/context/repo-map.ts:33` `regex symbols` 500 files + `recentFiles` `src/**` 15 + `deps` `package.json` → `truncated at last \n`
|
|
345
|
+
- **L7 live:** `RuntimeTelemetry` `src/context/level7.ts:60` `step/maxSteps/toolCallCount/inputTokens` `record*()` `format()` `1200` chars `emptyTelemetryBlock()` `summarize()` — injected via `systemPrompt(cwd,telemetry)` `src/agent/runtime.ts:240` each step
|
|
346
|
+
- **Tokenizer:** `estimateTokens(s)=ceil(len/4)` `src/context/tokenizer.ts:35` `compressTranscript()` `src/context/tokenizer.ts:76` deep-copy (was shallow mutate) + `consumed` set + `tool_result` 400 chars + `drop oldest` — **not wired into runtime** (dead code, transcript unbounded)
|
|
347
|
+
- **Selector:** `selectFiles()` `src/context/selector.ts:22` `+100 exact path` `+20 basename` — **not wired**
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 11. Verification & Repair (L6/L8)
|
|
352
|
+
|
|
353
|
+
- **auto:** `detectVerifyCommand(cwd)` `src/verification/auto.ts:10` `package.json test`→`npm test` / `tsconfig.json`→`tsc` / `Makefile` / `pytest` / `go test`
|
|
354
|
+
- **engine:** `verify({cwd,command,timeoutMs=5m})` `src/verification/engine.ts:28` `spawn shell true` `stdout+stderr` `timeout kill` `detect()` `diagnosticForModel()` `src/verification/engine.ts:67`
|
|
355
|
+
- **detect:** `detect(stdout,stderr,exitCode)` `src/verification/detect.ts:36` `type` `type|test|lint|build|runtime|unknown` `TS_LINE` `TEST_FAIL` `LINT_LINE` `RUNTIME_LINE` + `summarize()` 8 files
|
|
356
|
+
- **Runtime wiring:** `hasEdits` `src/agent/runtime.ts:175` `write_file/edit_file` → after `no tools` `finalText` → `verifyEnabled && hasEdits && verifyCmd` `src/agent/runtime.ts:331` → `verify()` → `verification_succeeded` → `complete` else `diagnosticForModel()` → `transcript.push(user repair)` `src/agent/runtime.ts:357` → `continue` ≤3 `maxRepairs` `src/agent/runtime.ts:330` → `verify_failed` `src/agent/runtime.ts:366` + `repair_started` event
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 12. Persistence (L9)
|
|
361
|
+
|
|
362
|
+
- **Store:** `SessionStore` `src/persistence/store.ts:52` `dir .sessions` `sessions.json` index + `{id}.json` `{record,messages,observations}` `randomUUID` `ensureDir` `readIndex` `writeIndex` `tmp→fsync→rename` `src/persistence/store.ts:74` (was non-atomic) + 3-attempt `writeSession` `src/persistence/store.ts:100`
|
|
363
|
+
- **Session:** `SessionRecord{id,cwd,task,status,createdAt,updatedAt,config,finalText}` `SessionStatus open|complete|verify_failed|aborted|max_steps` `src/persistence/store.ts:17` + `getDefaultSessionsDir()` `~/.klyro/sessions` `src/persistence/session.ts:10` `formatSession()` `resolveSessionId(prefix)` `src/persistence/session.ts:30`
|
|
364
|
+
- **Audit:** `AuditLog` `src/persistence/audit.ts:23` `AuditEvent` 11 kinds `JSONL appendFile`
|
|
365
|
+
- **Trace:** `TraceWriter` `src/trace/writer.ts:1` `JSONL .klyro/traces/<id>.jsonl` `appendFile + open/sync/close` on `tool.result` (was persistent `FileHandle` leak `src/trace/writer.ts:20` fixed)
|
|
366
|
+
- **Checkpoints:** `src/checkpoints/store.ts:1` `snapshot(cwd,files)` `.klyro/checkpoints/<id>` `listCheckpoints` `diff` `git diff --stat` `undo` `rewind` — **not wired to runtime snapshots** (runtime uses `SessionStore` checkpoint, not `checkpoints/store.ts`)
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 13. TUI (Professional, Full-Screen)
|
|
371
|
+
|
|
372
|
+
- **Stack:** `ink 7.1` `react 19` `ink-spinner 5` `react/jsx` `target ES2022` `module NodeNext`
|
|
373
|
+
- **Tokens** `src/tui/tokens.ts:1` `accent #8B7CF6/magenta` `muted #7A7A7A` `success #4ADE80` `error #F87171` `glyphs › ● ⎿ ✔ ✘ ⚠` + ASCII fallback `isAsciiMode()`
|
|
374
|
+
- **App** `src/tui/app.tsx:14` inline/scrollback per `TUI_DESIGN.md` `Static` history `src/tui/app.tsx:325` + batched `30fps` `src/tui/app.tsx:117` + single `useInput` `src/tui/app.tsx:185`
|
|
375
|
+
- `Banner` `src/tui/banner.tsx:1` `◆ Klyro v0.1.11` `cwd (branch ✎3)` `KLYRO.md` + resume `↻`
|
|
376
|
+
- `InputBox` `src/tui/input-box.tsx:1` `accent` `queued:` `shell` `!` `note` `#`
|
|
377
|
+
- `ThinkingBlock` `src/tui/thinking-block.tsx:1` `∴ Thinking… ctrl+t`
|
|
378
|
+
- `ActivityLine` `src/tui/activity-line.tsx:1` `✻ verb (4s · ↑1.2k)` + `CompactionDivider` `⟲`
|
|
379
|
+
- `Transcript` `src/tui/transcript.tsx:37` 6 kinds `ToolCard` `round` `cyan running` `green ✓`
|
|
380
|
+
- `StatusLine` `src/tui/status.tsx:23` `model step/max repairs tokens $0.043 ctx 6%`
|
|
381
|
+
- `Header` `src/tui/header.tsx:31` `abbrevPath`
|
|
382
|
+
- `DiffView` `src/tui/diff.tsx:57` `DiffHunk` `+ green` `- red` `context gray`
|
|
383
|
+
- `ApprovalModal` `src/tui/approval.tsx:60` `[y] once [a] session [A] always [n] [e] [?]` `TuiApprovalBridge` `src/tui/approval.tsx:31` promise `PendingPrompt`
|
|
384
|
+
- `PlanView` `src/tui/plan.tsx:44` `◯●✓✗⊘` `expanded`
|
|
385
|
+
- `DiffParser` `src/tui/diff-parser.ts:20` `parseUnifiedDiff`
|
|
386
|
+
- **Full-screen alt:** `src/tui/app.fullscreen.tsx:1` `width={width} height={height-1}` `Header` `KLYRO v0.1.9` `Conversation` `flexGrow` `Input` `StatusBar` (kept as `app.inline.tsx` backup)
|
|
387
|
+
- **History:** `~/.klyro/history` JSONL per project `src/tui/app.tsx:50` `loadHistory/appendHistory` `↑/↓` `Ctrl+R`
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 14. Policy & Safety
|
|
392
|
+
|
|
393
|
+
- **PathGuard** `src/policy/path-guard.ts:46` `resolveWithinCwd` `path.relative` + drive `PathGuardError`, `resolveAndFollowSymlinks` `realpath` `realParent` parent-symlink defense `src/policy/path-guard.ts:106`
|
|
394
|
+
- **Engine** `src/policy/engine.ts:84` `Decision allow|ask|deny` `PolicyConfig mode default|plan|accept-edits|auto + allow/deny/ask glob` `src/policy/engine.ts:34` `matchesGlobRule()` `src/policy/engine.ts:120` `.env` deny `src/policy/engine.ts:99` `additionalDirs` `src/policy/engine.ts:176` `shellDeny` `curl|sh` `src/policy/engine.ts:141`
|
|
395
|
+
- **Approval** `StdinApprovalPrompt` `DenyAll` `InMemoryAllowlist` `src/policy/approval.ts:21` + `TuiApprovalBridge`
|
|
396
|
+
- **Redactor** `src/policy/secret-redactor.ts:14` 7 patterns `aws-key` `aws-secret+b64` `pem-block` `github-token` `slack-token` `bearer` `jwt` `redact()` `createRedactor()` 1KiB tail
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 15. Eval Harness
|
|
401
|
+
|
|
402
|
+
- **Harness** `src/eval/harness.ts:70` `runTask()` `tmp klyro-eval-<id>` `ToolRegistry 9 tools` `verify()` + `runHarness()` `formatReport()` `compareReports()` `src/eval/harness.ts:180`
|
|
403
|
+
- **Tasks** `src/eval/tasks.ts:1` `MVP_TASKS 5` `t1 direct-answer` `t2 write-then-answer` `t3 policy-deny` `t4 max_steps` `t6 multitool`
|
|
404
|
+
- **File fixtures** `evals/fixtures/*` 10 smoke `read-answer` `add-fn-test` `fix-failing-test` … `task.md` `check.sh` `meta.json` `src/eval/harness.ts:130` `loadFileFixture` `runFileFixture` `bash -c`
|
|
405
|
+
- **Results** `evals/results/baseline.json` `8/10 80%` `src/eval/harness.ts:130`, `compareReports` `src/eval/harness.ts:180`, `klyro eval --suite smoke` `src/cli/eval.ts:84`
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## 16. CLI Surface
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
klyro # TUI REPL (isTTY ? TUI : legacy pipe)
|
|
413
|
+
klyro --tui / --no-tui / --chat # force
|
|
414
|
+
klyro -p "prompt" # headless positional (2.5)
|
|
415
|
+
klyro tui [-m model] [--max-steps n]
|
|
416
|
+
klyro run <prompt> [-m model] [--max-steps 30] [--max-cost $] [--max-time ms]
|
|
417
|
+
[--verify/--no-verify --verify-command <cmd> --max-repairs 3]
|
|
418
|
+
[--persist/--no-persist --resume-session <id>] [--output human|json|silent]
|
|
419
|
+
[--provider openai|anthropic] [--dry-run] [--resume <file>]
|
|
420
|
+
klyro chat [prompt] [-s system] # legacy
|
|
421
|
+
klyro eval [input] [--suite smoke --filter str --runs 1 --parallel 1 --output json]
|
|
422
|
+
klyro eval:compare <a> <b>
|
|
423
|
+
klyro config [list|get <key>|set <key> <value>|unset <key>|path|edit] # 5-layer Zod JSONC
|
|
424
|
+
klyro doctor [--json] # 7 checks
|
|
425
|
+
klyro completion <bash|zsh|fish|powershell>
|
|
426
|
+
klyro update # 24h cache
|
|
427
|
+
klyro login / logout [provider] # 0600 credentials.json, MODEL_ALIASES
|
|
428
|
+
klyro session list|show <id>|resume <id> [--json]
|
|
429
|
+
klyro resume <id> # alias
|
|
430
|
+
klyro trace <id> [--stats --json]
|
|
431
|
+
klyro --version / --help # global --cwd/--config/--debug/--json/--yes/--no-color
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Global flags `src/index.ts:60` `--cwd/--config/--debug/--verbose/--quiet/--json/--yes/--no-color` via `preAction` hook, `showSuggestionAfterError`.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 17. Testing
|
|
439
|
+
|
|
440
|
+
- **Config:** `vitest.config.ts:1` `include src/**/*.test` `environment node` `fileParallelism:false` `testTimeout 10_000`
|
|
441
|
+
- **Results:** `43 files 309/309` (was 36/288, +7 P0 L4)
|
|
442
|
+
- **Coverage:** No `--coverage` threshold; L4 new tests: `edit-file.test.ts:1` 10 tests (CRLF/BOM/trailing), `multi-edit` 2, `apply-patch` 2, `background` 2, `klyro-md` 2, `checkpoints` 1, `git` 2
|
|
443
|
+
- **Security gate:** `src/security.test.ts:1` 16 tests (cwd jail, policy, redaction, hostile)
|
|
444
|
+
- **Flaky:** `fileParallelism:false` hides concurrency bugs (`plan.md:199` parallel tools)
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 18. Known Gaps (Post-MVP)
|
|
449
|
+
|
|
450
|
+
- `compressTranscript()` `src/context/tokenizer.ts:76` deep-copy fixed but **not wired** into `runtime` (transcript unbounded)
|
|
451
|
+
- `verify` unbounded `stdout` `src/verification/engine.ts:31` no cap (vs `run_verify` 256k) — repair OOM
|
|
452
|
+
- `AuditLog` `src/persistence/audit.ts:23` defined but never written (dead code)
|
|
453
|
+
- `L6 repo-map` heuristic, no `tree-sitter`/`LSP` (plan L7)
|
|
454
|
+
- `L8+L9` wiring: `compressTranscript` + `checkpoints/store.ts` not used by `SessionStore`
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## 19. For a New Agent — Where to Start
|
|
459
|
+
|
|
460
|
+
1. **Runtime is king:** `src/agent/runtime.ts:150` `run()` — get this loop right, everything else is leaf.
|
|
461
|
+
2. **Provider contract:** `src/agent/provider-adapter.ts:42` `ProviderAdapter` + `src/tools/schema.ts:63` Zod→JSON — defines `multi-model`.
|
|
462
|
+
3. **Edit tool:** `src/tools/fs/edit-file.ts:26` — most used, diff-return shapes model reasoning.
|
|
463
|
+
4. **Verification:** `src/verification/engine.ts:28` + `detect.ts:36` — the “more reliable than prompting” claim.
|
|
464
|
+
5. **Config:** `src/cli/config.ts:10` Zod-derived types constrain every module.
|
|
465
|
+
|
|
466
|
+
Run: `pnpm build && pnpm test && node dist/index.js --help` → `klyro -p "fix login test" --output json` → `klyro doctor`.
|
|
467
|
+
|
package/dist/agent/runtime.js
CHANGED
|
@@ -264,7 +264,7 @@ export async function run(opts, deps) {
|
|
|
264
264
|
}
|
|
265
265
|
if (finalizedCalls.length === 0) {
|
|
266
266
|
finalText = textBuf;
|
|
267
|
-
// Level 8 — Verification + Autonomous Repair
|
|
267
|
+
// Level 8 — Verification + Autonomous Repair (gated on hasEdits below — pure analysis skips verify)
|
|
268
268
|
const verifyEnabled = opts.verify?.enabled !== false;
|
|
269
269
|
const verifyCmd = opts.verify?.command ?? detectVerifyCommand(opts.cwd);
|
|
270
270
|
const maxRepairs = opts.verify?.maxRepairAttempts ?? 3;
|
package/dist/cli/repl.js
CHANGED
|
@@ -330,17 +330,10 @@ export async function startRepl(opts = {}) {
|
|
|
330
330
|
queuedStatus({ status: 'done', repairs: result.repairs ?? 0 });
|
|
331
331
|
}
|
|
332
332
|
else {
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
queuedStatus({ status: 'done', repairs: result.repairs ?? 0 });
|
|
338
|
-
queuedAppend({ id: `no_final-${Date.now()}`, kind: 'text', text: `(no response — try /model ${model} or check provider logs)`, role: 'assistant' });
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
queuedStatus({ status: 'error', errorMessage: 'no final text' });
|
|
342
|
-
queuedAppend({ id: `no_final-${Date.now()}`, kind: 'error', message: 'Provider returned no final text — check model/provider (try /model or /doctor)' });
|
|
343
|
-
}
|
|
333
|
+
// Genuine provider error: stream ended without a final answer. Surface as a loud
|
|
334
|
+
// error header with /doctor guidance rather than hiding it behind a polite hint.
|
|
335
|
+
queuedStatus({ status: 'error', errorMessage: 'no final text' });
|
|
336
|
+
queuedAppend({ id: `no_final-${Date.now()}`, kind: 'error', message: 'Provider returned no final text — check model/provider (try /model or /doctor)' });
|
|
344
337
|
}
|
|
345
338
|
}
|
|
346
339
|
else {
|
package/dist/tui/app.js
CHANGED
|
@@ -160,5 +160,5 @@ export function App(props) {
|
|
|
160
160
|
const width = stdout?.columns ?? 100;
|
|
161
161
|
const height = stdout?.rows ?? 30;
|
|
162
162
|
const isSmall = width < 80;
|
|
163
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: height - 1, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsx(Text, { bold: true, children: "KLYRO v0.1.
|
|
163
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height - 1, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsx(Text, { bold: true, children: "KLYRO v0.1.15" }), _jsxs(Text, { color: tokens.ansi.muted, children: [status.model, " \u00B7 API Usage Billing \u00B7 step ", status.step, "/", status.maxSteps, " \u00B7 ", status.status, " \u00B7 repairs ", status.repairs] }), _jsx(Text, { color: tokens.ansi.muted, children: props.cwd })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingX: 1, paddingY: 1, children: [transcript.length === 0 ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: tokens.ansi.muted, children: "No conversation yet. Try \"fix the failing login test\"" }) })) : (transcript.map((item) => (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: item.kind === 'text' && item.role === 'user' ? (_jsxs(Text, { children: ["\u203A ", item.text] })) : item.kind === 'text' ? (_jsxs(Text, { children: [" ", item.text] })) : item.kind === 'tool' ? (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsxs(Text, { children: [item.name, " ", item.isError ? '✗' : '✓', " ", item.latencyMs ?? 0, "ms"] }), item.result ? _jsx(Text, { color: tokens.ansi.muted, children: String(item.result).slice(0, 200) }) : null] })) : item.kind === 'diff' ? (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsx(Text, { bold: true, children: item.summary ?? 'Diff' }), item.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: tokens.ansi.info, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { color: l.kind === 'add' ? tokens.ansi.success : l.kind === 'remove' ? tokens.ansi.error : tokens.ansi.muted, children: [l.kind === 'add' ? '+ ' : l.kind === 'remove' ? '- ' : ' ', l.text] }, j)))] }, i)))] })) : (_jsx(Transcript, { items: [item] })) }, item.id)))), liveText ? (_jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { children: [liveText, "\u258D"] }) })) : status.status === 'running' ? (_jsxs(Box, { children: [_jsx(Text, { color: tokens.ansi.info, children: "\u2726 Thinking..." }), _jsxs(Text, { color: tokens.ansi.muted, children: [" \u00B7 ", Math.round(elapsed / 1000), "s"] })] })) : null, plan.length > 0 ? _jsx(PlanView, { steps: plan, expanded: false, onToggle: () => { } }) : null] }), _jsxs(Box, { borderStyle: "single", borderColor: tokens.ansi.accent, paddingX: 1, children: [_jsx(Text, { children: "\u203A " }), _jsxs(Text, { children: [input, "\u258F"] })] }), _jsxs(Box, { justifyContent: "space-between", paddingX: 1, borderStyle: "single", borderColor: tokens.ansi.border, children: [_jsxs(Text, { color: tokens.ansi.muted, children: [status.model, " \u00B7 ", status.usageInput + status.usageOutput, " tokens \u00B7 $", (status.usageInput / 1000 * 0.003 + status.usageOutput / 1000 * 0.015).toFixed(2), " \u00B7 ", Math.round(elapsed / 1000), "s"] }), _jsx(Text, { color: tokens.ansi.muted, children: isSmall ? 'Ctrl+C interrupt' : 'Ctrl+C interrupt · Ctrl+O expand · ↑↓ scroll' })] })] }));
|
|
164
164
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klyro",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Klyro — autonomous coding harness CLI that streams from any OpenAI-compatible or Anthropic LLM endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"README.md",
|
|
15
|
+
"READ.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
17
18
|
"engines": {
|