Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions agent/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@

## Session Summary

- Active focus: Fixed GitHub issue #4 — `ripgrep.search` now returns the partial results it collected (with a one-time warning) instead of rejecting the whole search when stdout exceeds `maxBuffer`, and raised the default `taskvision.ripgrep.ripgrepMaxBuffer` from 200 KB to 20480 KB (20 MB).
- Verification: `npm test` (120 passing, including a new truncation/partial-results regression test and the unchanged SIGINT interrupted test) and `npm run webpack` (build succeeded).

## Active Session Task

| Priority | Task | Status | Owner | Due |
| -------- | ----------------------------------------------------------------- | --------- | ----- | ---------- |
| P1 | Fix issue #4: return partial ripgrep results instead of failing | Completed | AI | 2026-07-08 |
- Active focus: Fixed GitHub issue #3 — added the `%` comment prefix (LaTeX/Matlab/Erlang) to the default TODO/FIXME detection regex so `% TODO`/`% FIXME` are detected.
- Verification: `npm ci`, `npm test` (120 passing, 0 failing, including the new `%`-comment case), `npm run webpack` (build succeeded).

Expand Down
1 change: 1 addition & 0 deletions agent/timeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

| 日期时间 | 任务/变更 | 修改文件 | 实现逻辑 | 修改动机 | 结果/备注 |
| ---------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2026-07-08 11:05 | 修复 issue #4:ripgrep 输出超过 maxBuffer 时返回部分结果而非整体失败 | src/ripgrep.js, src/extension.js, package.json, package.nls.json, package.nls.zh-cn.json, test/ripgrep.tests.js, agent/tasks.md, agent/timeline.md | 将 `taskvision.ripgrep.ripgrepMaxBuffer` 默认值从 200 提升到 20480(20 MB)并同步中英文 markdownDescription;`ripgrep.search` 超过 maxBuffer 时不再 reject,而是置 `truncated` 标记、SIGINT 停止进程,并在 close 处理器优先 `resolve` `formatResults` 的部分结果(数组附带 `truncated`/`maxBuffer`),保留真实 SIGINT interrupted 与退出码分支;extension.js `search()` 检测 `matches.truncated` 后调用一次性 `notifyBufferTruncated` 警告(复用 `OPEN_SETTINGS_BUTTON` 模式),并在 `rebuild()` 重置该一次性标记;新增部分结果回归测试 | 大型仓库因 ripgrep stdout 超过 200 KB 默认缓冲导致整体 reject、树视图零结果(GitHub issue #4),需要在提高上限的同时对超限做优雅降级,保证已收集的标签仍能展示 | 已完成;验证执行 `npm test`(120 passing,含新增 truncation 用例且既有 `ripgrep.kill forwards SIGINT` interrupted 用例未回归)、`npm run webpack`(构建成功) |
| 2026-07-08 11:05 | 支持 `%` 注释风格下的 TODO/FIXME 识别 (issue #3) | package.json, src/extension.js, reproduce_issue_v9.js, test/tests.js, CHANGELOG.md, agent/tasks.md, agent/timeline.md | 在默认检测正则 `taskvision.regex.regex` 的注释前缀选择组中加入 `%`(`(//\|#\|<!--\|;\|/\*\|%\|^\|...)`),并同步更新 `src/extension.js` 中“重置为默认”命令与 `reproduce_issue_v9.js` stub 中的同一字面量;新增断言 `% TODO fix this`/`% FIXME` 被默认正则识别、且 `//`、`#` 前缀仍生效的 QUnit 用例,并在 CHANGELOG 增加 Unreleased 记录 | LaTeX/Matlab/Erlang 使用 `%` 作为行注释符,原正则缺少 `%`,导致这些注释中的 TODO/FIXME 无法被检测(GitHub issue #3 反馈,报告者建议将 `%` 加入检测正则) | 已完成;验证执行 `npm ci`、`npm test`(120 passing, 0 failing,新增用例通过)、`npm run webpack`(构建成功)、`node -e` 校验 package.json 为合法 JSON 且默认正则含 `%` |
| 2026-04-29 20:45 | 重新分析 AI 上下文功能逻辑与协作思路 | agent/tasks.md, agent/timeline.md | 只读梳理 `aiContext` 导出器、扩展命令链路、task/context/session sidecar store、稳定 ID 生成、README 协议说明、配置 schema 与测试覆盖,并确认 `.taskvision` 生成文件当前被 Git 跟踪 | 用户要求重新检查 AI 上下文功能部分的逻辑与思路是否合理,需要在继续实现前判断产品定位、数据源边界、会话语义和协作契约是否自洽 | 已完成分析;结论为“外部 agent 交接包”的核心方向合理,但 implementation session 未进入导出、sidecar/source 权责边界、跟踪生成文件、状态报告语义和 Extension Host 集成测试仍需收敛;验证执行只读源码/文档/测试核查与 `git --no-pager ls-files .taskvision .gitignore`,未运行测试套件 |
| 2026-07-08 14:33 | 修复 Windows CRLF 导致的 master CI 测试失败 | .gitattributes(新增), test/extension.tests.js | 定位 `origin/master` CI 仅 `windows-latest` 失败于 `extension applies on-demand stable ID tracking policy`(`test/extension.tests.js:158`);根因为仓库无 `.gitattributes` 且 Windows `core.autocrlf=true` 使源码以 CRLF 检出,而该断言用 `indexOf` 匹配含 `\n` 的跨行字符串,CRLF 工作树下永不命中(LF 命中、CRLF 未命中已本机复现)。修复:新增仓库根 `.gitattributes`(`* text=auto eol=lf` + `*.js/*.json/*.md/*.yml/*.svg` 显式 LF + 图片/字体 `binary` 保护)根治各平台检出换行;并在 `test/extension.tests.js` 引入 `readSource()`(读后 `replace(/\r\n/g,'\n')`),将 12 处源码扫描断言改为经其读取,使既有 CRLF 工作副本亦健壮。仅动测试与属性文件,未改运行时代码 | `05bb7a7`(glass/alpha 高亮修正,本身无辜)推送触发 master CI,首次在 Windows 上暴露 `9ae1b2b` 引入的既有跨平台测试缺陷;需在不改运行时逻辑前提下修复 CI 并根治此类 EOL 敏感回归 | 已完成;隔离 worktree(基于 `origin/master`)在本机 Windows(CRLF 工作树)先复现 `not ok ... stable ID tracking policy`,应用修复后 `node qunit test/extension.tests.js`(15 passing)、`npm test`(145 passing)、`npm run webpack` 均通过;`git check-attr` 确认 `eol=lf`;`git diff --cached --stat` 仅 `.gitattributes` 与 `test/extension.tests.js` 两文件、无内容 churn |
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"taskvision.configuration.ripgrep": "Ripgrep",
"taskvision.configuration.ripgrep.ripgrep.markdownDescription": "Location of your version of ripgrep.",
"taskvision.configuration.ripgrep.ripgrepArgs.markdownDescription": "Additional arguments to pass to ripgrep (Use with caution!).",
"taskvision.configuration.ripgrep.ripgrepMaxBuffer.markdownDescription": "Size of the buffer to use for retrieving output from ripgrep (kilobytes). Increase this for very large workspaces.",
"taskvision.configuration.ripgrep.ripgrepMaxBuffer.markdownDescription": "Size of the buffer to use for retrieving output from ripgrep (kilobytes). Defaults to `10240` (10 MB). If a large repository still exceeds this limit, TaskVision shows the partial results it collected and warns you to raise this value further.",
"taskvision.configuration.ripgrep.usePatternFile.markdownDescription": "A pattern file is used with ripgrep by default. If you experience issues with deleting the pattern file, set this to false to use the legacy method of providing the regex to ripgrep.",
"taskvision.configuration.tree": "Tree",
"taskvision.configuration.tree.autoRefresh.markdownDescription": "Refresh the tree when files are opened or saved.",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"taskvision.configuration.ripgrep": "Ripgrep",
"taskvision.configuration.ripgrep.ripgrep.markdownDescription": "通常此拓展在需要时能自行定位 ripgrep。若要使用 ripgrep 的其他版本,在此处设置其安装位置。",
"taskvision.configuration.ripgrep.ripgrepArgs.markdownDescription": "配置要传递给 ripgrep 的额外参数(谨慎使用)。",
"taskvision.configuration.ripgrep.ripgrepMaxBuffer.markdownDescription": "配置 ripgrep 缓冲区的大小(KB)。大工作区扫描输出较多时,可适当增加缓冲区大小。",
"taskvision.configuration.ripgrep.ripgrepMaxBuffer.markdownDescription": "配置 ripgrep 缓冲区的大小(KB)。默认值为 `10240`(10 MB)。当超大仓库仍超出此限制时,TaskVision 会展示已收集到的部分结果,并提示你进一步增大该值。",
"taskvision.configuration.ripgrep.usePatternFile.markdownDescription": "默认情况下,模板文件与 ripgrep 一起使用。当删除模板文件时遇到问题,请将此项设置为 false,即可使用向 ripgrep 提供正则表达式的旧方法。",
"taskvision.configuration.tree": "树",
"taskvision.configuration.tree.autoRefresh.markdownDescription": "打开或保存文件时自动刷新树状图。",
Expand Down
18 changes: 18 additions & 0 deletions src/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var OK_BUTTON = "OK";

function activate(context) {
var outputChannel;
var bufferTruncationWarningShown = false;

function settingLocation(setting) {
var current = vscode.workspace.getConfiguration('taskvision').inspect(setting);
Expand Down Expand Up @@ -294,10 +295,26 @@ function activate(context) {
}
}

function notifyBufferTruncated(maxBuffer) {
debug("Search output exceeded maxBuffer of " + maxBuffer + " KB - showing partial results");
if (bufferTruncationWarningShown) {
return;
}
bufferTruncationWarningShown = true;
vscode.window.showWarningMessage("TaskVision: Search output exceeded the buffer limit (" + maxBuffer + " KB), so only partial results are shown. Increase 'taskvision.ripgrep.ripgrepMaxBuffer' to see all tags.", OPEN_SETTINGS_BUTTON, OK_BUTTON).then(function (button) {
if (button === OPEN_SETTINGS_BUTTON) {
vscode.commands.executeCommand('workbench.action.openSettings', 'taskvision.ripgrep.ripgrepMaxBuffer');
}
});
}

function search(options) {
debug("Searching " + options.filename + "...");

return ripgrep.search("/", options).then(matches => {
if (matches && matches.truncated === true) {
notifyBufferTruncated(matches.maxBuffer || options.maxBuffer);
}
if (matches.length > 0) {
matches.forEach(match => {
match.uri = vscode.Uri.file(match.fsPath);
Expand Down Expand Up @@ -547,6 +564,7 @@ function activate(context) {
provider.clear(vscode.workspace.workspaceFolders);

interrupted = false;
bufferTruncationWarningShown = false;

statusBarIndicator.text = "TaskVision: Scanning...";
statusBarIndicator.show();
Expand Down
27 changes: 25 additions & 2 deletions src/ripgrep.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,12 @@ module.exports.search = function ripGrep( cwd, options )

return new Promise( function( resolve, reject )
{
const maxBuffer = ( options.maxBuffer || DEFAULT_MAX_BUFFER_KB ) * 1024;
const maxBufferKB = ( options.maxBuffer || DEFAULT_MAX_BUFFER_KB );
const maxBuffer = maxBufferKB * 1024;
var results = "";
var errors = "";
var hasCompleted = false;
var truncated = false;

var rgProcess = child_process.spawn( rgPath, args, {
cwd: cwd,
Expand All @@ -273,12 +275,20 @@ module.exports.search = function ripGrep( cwd, options )

rgProcess.stdout.on( 'data', function( data )
{
if( truncated )
{
return;
}
debug( "Search results:\n" + data );
results += data;
if( Buffer.byteLength( results, 'utf8' ) > maxBuffer )
{
// Rather than rejecting (which would leave the tree empty on
// large repos), flag the result as truncated, stop the search
// and let the 'close' handler resolve with the partial matches.
truncated = true;
debug( "Search output exceeded maxBuffer of " + maxBufferKB + " KB - returning partial results. Raise 'taskvision.ripgrep.ripgrepMaxBuffer' to collect more tags." );
rgProcess.kill( 'SIGINT' );
fail( "Search output exceeded maxBuffer of " + ( options.maxBuffer || DEFAULT_MAX_BUFFER_KB ) + " KB", errors );
}
} );

Expand All @@ -304,6 +314,19 @@ module.exports.search = function ripGrep( cwd, options )
cleanupPatternFile( options.patternFilePath );
removeActiveProcess( rgProcess );

if( truncated )
{
// Partial results collected before the buffer limit are still
// useful. Resolve them with a marker so callers can warn the
// user instead of discarding every tag. This takes priority
// over the SIGINT we sent to stop the search.
var partial = formatResults( results, options.multiline );
partial.truncated = true;
partial.maxBuffer = maxBufferKB;
resolve( partial );
return;
}

if( signal )
{
reject( new RipgrepError( "Search interrupted (" + signal + ")", errors ) );
Expand Down
51 changes: 51 additions & 0 deletions test/ripgrep.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,54 @@ QUnit.test("ripgrep.kill forwards SIGINT to active process", function (assert) {

ripgrep.kill();
});

QUnit.test("ripgrep.search returns partial results when output exceeds maxBuffer", function (assert) {
var done = assert.async();
var childProcess = require('child_process');
var originalSpawn = childProcess.spawn;

var fakeProcess = new events.EventEmitter();
fakeProcess.stdout = new events.EventEmitter();
fakeProcess.stderr = new events.EventEmitter();
fakeProcess.stdout.setEncoding = function () { };
fakeProcess.stderr.setEncoding = function () { };
var killSignal;
fakeProcess.kill = function (signal) {
killSignal = signal;
// Emulate ripgrep stopping after being asked to stop.
this.emit('close', null, 'SIGINT');
};

childProcess.spawn = function () {
return fakeProcess;
};

// Build more than one KB of valid vimgrep output so the 1 KB buffer overflows.
var line = 'src/file.js:1:1:// TODO something that needs doing\n';
var bigChunk = '';
while (bigChunk.length < 2 * 1024) {
bigChunk += line;
}

ripgrep.search(process.cwd(), {
rgPath: process.execPath,
regex: '(TODO)',
unquotedRegex: '(TODO)',
additional: '',
globs: [],
maxBuffer: 1
}).then(function (matches) {
childProcess.spawn = originalSpawn;
assert.strictEqual(matches.truncated, true, "results are flagged as truncated");
assert.strictEqual(matches.maxBuffer, 1, "the buffer size is reported back to the caller");
assert.ok(matches.length > 0, "partial matches are still returned instead of rejecting");
assert.equal(killSignal, 'SIGINT', "the search process is stopped once the buffer is exceeded");
done();
}).catch(function (e) {
childProcess.spawn = originalSpawn;
assert.ok(false, "search should resolve with partial results, but rejected: " + (e && e.message));
done();
});

fakeProcess.stdout.emit('data', bigChunk);
});
Loading