@noobdemon/noob-cli 1.12.3 → 1.12.5
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 +10 -0
- package/package.json +1 -1
- package/src/agent.js +1 -0
- package/src/repl.js +92 -38
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Tất cả thay đổi đáng kể của `@noobdemon/noob-cli` được ghi vào file này.
|
|
4
4
|
|
|
5
|
+
## [1.12.5] - 2026-06-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Rule VERIFY-BEFORE-DISMISS** (`src/agent.js` SYSTEM + `noob.md` Rules): chống over-confidence ngược chiều với ANTI-HALLUCINATION. Model trước đây gặp TOOL RESULT trông lạ (output từ phiên cũ, lệnh không khớp) là tự phán "giả/noise/injection" rồi bỏ qua — cùng bản chất hallucination success (thay evidence bằng memory). Giờ default = tin runtime, nghi ngờ bản thân: thấy result lạ → coi như THẬT → chạy 1 tool xác minh (`read_file`/`grep`/`list_dir`/re-run) → chỉ khi tool xác minh MÂU THUẪN mới được gọi stale, và làm việc theo kết quả MỚI.
|
|
9
|
+
|
|
10
|
+
## [1.12.4] - 2026-06-12
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Bảng markdown không render khi stream** (`src/repl.js`): fix `1.12.3` chỉ áp dụng cho path NON-STREAM (`printAnswer` → `renderMarkdown`). Khi stream từng dòng qua `makeStreamPrinter`, mỗi dòng render độc lập qua `renderStreamLine` (chỉ hiểu heading + inline) → bảng `| ... |` đi raw ra terminal. Giờ thêm `renderStreamBlock(lines, fenceState)` export, detect table bằng cặp header-row + separator regex `TABLE_SEP_RE` (`/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/`), gom các row liên tiếp → 1 call `renderMarkdown`. `flushCompleteLines` HOLD các dòng cuối là table-row/sep candidate (max 64 dòng) để chờ đủ data — nếu flush ngay khi gặp `\n` thì header bị emit trước, separator đến sau không gom được. Cả `push(beforeTool)` và `flush()` cũng route qua `renderStreamBlock`. Code fence vẫn bypass ưu tiên cao nhất. Smoke `scripts/smoke-table-stream2.mjs` 5/5 PASS (table đơn, prose+table+prose, no-table regression, code fence bypass, false-positive pipe in prose).
|
|
14
|
+
|
|
5
15
|
## [1.12.3] - 2026-06-12
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -36,6 +36,7 @@ Context is finite. Don't slurp the whole repo up front. Discover information pro
|
|
|
36
36
|
# Rules
|
|
37
37
|
- TODO-BASED EXECUTION: For multi-step tasks, you MUST keep going until ALL items are "- [x]". NEVER stop mid-list. Flow: (1) write todo list, (2) start first item, (3) after EVERY tool result, check off the completed item AND IMMEDIATELY start the next unchecked item, (4) repeat until all done. Your response is NOT finished until ALL items are checked. The ONLY valid reason to stop is: (a) all items done, or (b) you are WAITING for a user reply. If you just got a tool result, you MUST continue — do NOT output a summary, do NOT ask "what next", do NOT stop. After write_file/edit_file returns, immediately do the next item.
|
|
38
38
|
- GROUND TRUTH = real TOOL RESULTs in this conversation, not your memory or what you intended to do. A file changed only if a write_file/edit_file result confirms it (see the FILES CHANGED list). A test passed / build succeeded / command worked only if a run_command result above shows it. Never narrate outcomes you didn't observe; if you haven't checked, say so and check now (read_file / list_dir / run the command). Before any "done/summary" reply, reconcile every file and result you're about to claim against the actual tool results above — if it isn't there, you didn't do it yet.
|
|
39
|
+
- VERIFY BEFORE DISMISSING: never declare a TOOL RESULT "fake", "spurious", "injected", "unrelated", or "from a previous turn" without first verifying with a fresh tool call. If a result looks off (unexpected content, output you didn't ask for, weird command), your DEFAULT is: treat it as REAL runtime output, then run a small verification (read_file the affected path, grep for the symbol, list_dir, re-run the command) to confirm actual state. Only after the verification tool result contradicts the suspicious one may you call it stale/leftover — and even then, work from the FRESH result, never from your guess. Trusting your own skepticism over the runtime is the same over-confidence bug as hallucinating success: both substitute memory for evidence.
|
|
39
40
|
- Investigate before editing: read the relevant files first; never invent file contents.
|
|
40
41
|
- Make the smallest change that fully solves the task. Match the surrounding code style.
|
|
41
42
|
- Prefer edit_file over write_file for existing files.
|
package/src/repl.js
CHANGED
|
@@ -2138,12 +2138,69 @@ function printAnswer(text, name, color) {
|
|
|
2138
2138
|
// - Trên dòng đang dở (chưa thấy \n), giữ trong buffer; cuối cùng `flush()` xử lý.
|
|
2139
2139
|
// - Block code fence (```lang ... ```) bypass renderer: in nguyên xi giữa cặp ```.
|
|
2140
2140
|
// - ```tool trở đi: nuốt sạch (sẽ render riêng qua tool dispatcher).
|
|
2141
|
+
// - Bảng markdown (header + separator |---|) cần parse đa dòng → gom thành
|
|
2142
|
+
// table block, render qua marked.parse() 1 lần (renderStreamBlock).
|
|
2143
|
+
// QUAN TRỌNG: stream line-by-line không tương thích với block elements
|
|
2144
|
+
// đa dòng — flushCompleteLines phải HOLD các dòng cuối là table-row/sep
|
|
2145
|
+
// candidate để chờ đủ data trước khi parse.
|
|
2141
2146
|
function renderStreamLine(line, inCodeFence) {
|
|
2142
2147
|
if (inCodeFence) return line; // code fence body: in raw, không parse markdown.
|
|
2143
2148
|
const heading = renderHeadingLine(line);
|
|
2144
2149
|
if (heading !== null) return heading;
|
|
2145
2150
|
return renderInline(renderBulletPrefix(line));
|
|
2146
2151
|
}
|
|
2152
|
+
// Dòng separator của bảng markdown: |---|---| hoặc |:---:|---:| v.v.
|
|
2153
|
+
const TABLE_SEP_RE = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/;
|
|
2154
|
+
// Dòng có ít nhất 1 pipe ở giữa (không tính pipe đầu/cuối) → ứng viên row của bảng.
|
|
2155
|
+
function looksLikeTableRow(line) {
|
|
2156
|
+
const trimmed = line.trim();
|
|
2157
|
+
if (!trimmed.startsWith('|') && !trimmed.includes('|')) return false;
|
|
2158
|
+
// Cần ít nhất 1 ký tự `|` thực sự phân tách cột.
|
|
2159
|
+
const stripped = trimmed.replace(/^\|/, '').replace(/\|$/, '');
|
|
2160
|
+
return stripped.includes('|');
|
|
2161
|
+
}
|
|
2162
|
+
// Render block lines (mảng dòng KHÔNG có \n cuối) thành ANSI. Detect table block
|
|
2163
|
+
// và parse qua marked; phần còn lại render line-by-line như cũ.
|
|
2164
|
+
export function renderStreamBlock(lines, fenceState) {
|
|
2165
|
+
const out = [];
|
|
2166
|
+
let i = 0;
|
|
2167
|
+
while (i < lines.length) {
|
|
2168
|
+
const ln = lines[i];
|
|
2169
|
+
// Code fence ưu tiên cao nhất — không đụng vào nội dung fence.
|
|
2170
|
+
const fenceMatch = ln.match(/^\s*```/);
|
|
2171
|
+
if (fenceMatch) {
|
|
2172
|
+
out.push(renderStreamLine(ln, fenceState.inCodeFence));
|
|
2173
|
+
fenceState.inCodeFence = !fenceState.inCodeFence;
|
|
2174
|
+
i++;
|
|
2175
|
+
continue;
|
|
2176
|
+
}
|
|
2177
|
+
if (fenceState.inCodeFence) {
|
|
2178
|
+
out.push(ln);
|
|
2179
|
+
i++;
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
// Detect table: dòng hiện tại là row + dòng kế tiếp là separator.
|
|
2183
|
+
if (
|
|
2184
|
+
looksLikeTableRow(ln) &&
|
|
2185
|
+
i + 1 < lines.length &&
|
|
2186
|
+
TABLE_SEP_RE.test(lines[i + 1])
|
|
2187
|
+
) {
|
|
2188
|
+
// Gom hết các dòng row liên tiếp.
|
|
2189
|
+
const tableLines = [ln, lines[i + 1]];
|
|
2190
|
+
let j = i + 2;
|
|
2191
|
+
while (j < lines.length && looksLikeTableRow(lines[j])) {
|
|
2192
|
+
tableLines.push(lines[j]);
|
|
2193
|
+
j++;
|
|
2194
|
+
}
|
|
2195
|
+
out.push(renderMarkdown(tableLines.join('\n')));
|
|
2196
|
+
i = j;
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
out.push(renderStreamLine(ln, false));
|
|
2200
|
+
i++;
|
|
2201
|
+
}
|
|
2202
|
+
return out.join('\n');
|
|
2203
|
+
}
|
|
2147
2204
|
function makeStreamPrinter(name, color) {
|
|
2148
2205
|
let buf = ''; // toàn bộ delta đã nhận (chưa cắt)
|
|
2149
2206
|
let printed = 0; // offset đã in trong buf
|
|
@@ -2164,28 +2221,36 @@ function makeStreamPrinter(name, color) {
|
|
|
2164
2221
|
};
|
|
2165
2222
|
// Render & emit từ printed → end. Chỉ flush các dòng đã HOÀN CHỈNH (có \n).
|
|
2166
2223
|
// Dòng cuối chưa có \n: chừa lại trong buf cho lần push tiếp theo.
|
|
2224
|
+
// Lùi mốc flush nếu các dòng cuối là table-row/separator candidate — chờ đủ
|
|
2225
|
+
// data để gom thành table block parse 1 lần qua marked (xem renderStreamBlock).
|
|
2167
2226
|
const flushCompleteLines = (end) => {
|
|
2168
2227
|
const slice = buf.slice(printed, end);
|
|
2169
2228
|
if (!slice) return;
|
|
2170
2229
|
const lastNl = slice.lastIndexOf('\n');
|
|
2171
|
-
if (lastNl === -1) return;
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2230
|
+
if (lastNl === -1) return;
|
|
2231
|
+
let complete = slice.slice(0, lastNl + 1);
|
|
2232
|
+
let completeLines = complete.split('\n');
|
|
2233
|
+
if (completeLines[completeLines.length - 1] === '') completeLines.pop();
|
|
2234
|
+
let hold = 0;
|
|
2235
|
+
while (
|
|
2236
|
+
completeLines.length - hold > 0 &&
|
|
2237
|
+
(looksLikeTableRow(completeLines[completeLines.length - 1 - hold]) ||
|
|
2238
|
+
TABLE_SEP_RE.test(completeLines[completeLines.length - 1 - hold]))
|
|
2239
|
+
) {
|
|
2240
|
+
hold++;
|
|
2241
|
+
if (hold > 64) break;
|
|
2242
|
+
}
|
|
2243
|
+
if (hold > 0) {
|
|
2244
|
+
const keptLines = completeLines.slice(0, completeLines.length - hold);
|
|
2245
|
+
if (keptLines.length === 0) return; // toàn bộ slice là table candidate → chờ
|
|
2246
|
+
complete = keptLines.join('\n') + '\n';
|
|
2247
|
+
completeLines = keptLines;
|
|
2248
|
+
}
|
|
2249
|
+
printed += complete.length;
|
|
2250
|
+
const fenceState = { inCodeFence };
|
|
2251
|
+
const rendered = renderStreamBlock(completeLines, fenceState);
|
|
2252
|
+
inCodeFence = fenceState.inCodeFence;
|
|
2253
|
+
emit(rendered + '\n');
|
|
2189
2254
|
};
|
|
2190
2255
|
return {
|
|
2191
2256
|
get started() {
|
|
@@ -2203,17 +2268,12 @@ function makeStreamPrinter(name, color) {
|
|
|
2203
2268
|
// vì sắp suppress, không cần chừa buffer nữa).
|
|
2204
2269
|
const beforeTool = buf.slice(printed, f);
|
|
2205
2270
|
if (beforeTool) {
|
|
2206
|
-
// Render từng dòng kể cả dòng dở cuối
|
|
2207
2271
|
const parts = beforeTool.split('\n');
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
return out;
|
|
2214
|
-
}
|
|
2215
|
-
return renderStreamLine(ln, inCodeFence);
|
|
2216
|
-
}).join('\n');
|
|
2272
|
+
// bỏ '' cuối nếu kết thúc bằng \n
|
|
2273
|
+
if (parts[parts.length - 1] === '') parts.pop();
|
|
2274
|
+
const fenceState = { inCodeFence };
|
|
2275
|
+
const rendered = renderStreamBlock(parts, fenceState);
|
|
2276
|
+
inCodeFence = fenceState.inCodeFence;
|
|
2217
2277
|
emit(rendered);
|
|
2218
2278
|
}
|
|
2219
2279
|
printed = buf.length;
|
|
@@ -2229,16 +2289,10 @@ function makeStreamPrinter(name, color) {
|
|
|
2229
2289
|
// Render phần còn lại (kể cả dòng cuối chưa có \n).
|
|
2230
2290
|
const tail = buf.slice(printed);
|
|
2231
2291
|
const parts = tail.split('\n');
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
const out = renderStreamLine(ln, inCodeFence);
|
|
2237
|
-
inCodeFence = !inCodeFence;
|
|
2238
|
-
return out;
|
|
2239
|
-
}
|
|
2240
|
-
return renderStreamLine(ln, inCodeFence);
|
|
2241
|
-
}).join('\n');
|
|
2292
|
+
if (parts[parts.length - 1] === '') parts.pop();
|
|
2293
|
+
const fenceState = { inCodeFence };
|
|
2294
|
+
const rendered = renderStreamBlock(parts, fenceState);
|
|
2295
|
+
inCodeFence = fenceState.inCodeFence;
|
|
2242
2296
|
emit(rendered);
|
|
2243
2297
|
printed = buf.length;
|
|
2244
2298
|
}
|