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
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ npm run delivery:verify

`check` inventories every `*.test.ts`, type-checks, builds the Gateway, restores native dependencies in locked mode, builds the Release Host and console fixtures, runs non-interactive regression and fresh-process stdio checks, then creates and verifies `dist/delivery-manifest.json`. `check:desktop` verifies that delivery, publishes the isolated WPF fixture and runs UI plus UI-to-source tests in an interactive Windows session. `test:all` runs both. Real Roslyn Host and MCP checks run on Node 22 in CI; TavernDesk checks remain opt-in. No check installs global prerequisites or changes client configuration.

The published Tray endpoint check runs in the stdio stage after parallel regression. It uses the production resolver and its unchanged three-second timeout, records the elapsed time, and does not open the Tray UI. A failure still fails `check`; it is not skipped or retried. Tray pipe-protocol tests remain in the parallel regression set.

Reports and bounded stage logs are under `test-tmp/check/<run>/`. CI retains the report, captured stage logs and Node's JUnit test reports for seven days, including failed runs; it does not upload local workspaces or screenshots. Test stages record exit status, TAP totals and the JUnit path before propagating failure, so an early assertion remains available after later passing output. Capture keeps the existing 8 MiB process-output budget; launch, timeout or overflow errors set `outputCaptureComplete=false`, and missing TAP totals cannot pass even with exit code zero. A JUnit path alone does not prove a run completed. A passing core check does not establish desktop or real-upstream acceptance.

`npm run test:roslyn-host` and `npm run test:roslyn-gateway` use generated C# projects and an already installed SDK selected by `scripts/lib/dotnet.mjs`. The gateway check copies the entire published Code Host into a Chinese path with spaces and checks real overloads, stale identities and owned MSBuild descendants. This is not a clean-machine test or verification of the current Codex connection.
Expand Down
8 changes: 8 additions & 0 deletions docs/codex_worklog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1333,3 +1333,11 @@
- 本地 Node 24.19.0:Gateway 构建和类型检查通过;上下文、导航、扫描、工具契约与 UI 候选集合 86/86;生产固定工作区集合 8/8;修订后的 MCP 恢复场景 1/1;真实 WPF 源码修改与重编译流程 1/1。正式 STDIO 的 17 工具、搜索/概览/EOF 续读、错误契约 17 场景通过。原生三组件经锁定 restore 和正式 Release 发布,交付清单校验通过。原始失败记录保留;本轮未重复完整本地 core/desktop 集合,完整兼容性检查交给最终 PR 提交的 CI。分阶段回执:test-tmp/navigation-delivery/report.json。
- 实际 Codex 连接已在本次核对前刷新:实例 44103150-ef65-41e1-9f61-db8162ef752f,17 个工具、buildId=866c93db7b87c8a5a5f5975f8c81ad601b01c02477a562b837414fbd9f344ba3,与独立 worktree 构建相同。通过该连接完成限定目录搜索、文件概览、按返回请求读取 25/25 行;针对本轮启动的隔离 WPF 窗口完成 compact 读取及 full 展开,原生查询唯一,控件状态和几何恢复正确。回执:test-tmp/navigation-delivery/live-client.json。未改 MCP 配置;源码和 Schema 无后续变化,不要求再次重启当前连接。
- 安装的 Skill 已通过既有脚本备份并同步,仅更新两份手册的策略版本,入口仍为 22 行;备份 .wincode-backup-946fa519-9d5c-4008-ba14-e3ac6fdf27dd,校验 matched=true。最终 PR-head 的 Node 22/24、三项 CodeQL 和合并后 main 检查仍待运行;不沿用 PR #39 的成功结果。

## 2026-09-11 — 合并后 Node 22 失败与 worktree 浏览修正

- PR #40 的最终提交 7ceeeff 在 Node 22/24 和三项 CodeQL 首次检查全部通过后合并为 0944051;随后 main CI 34584238856 的 Node 22 回归失败。唯一失败项为 Tray 端点解析,耗时 3049.8471 ms,原生进程因生产 resolver 的 3000 ms 超时被终止;尚无证据区分 .NET 冷启动、系统负载或其他启动延迟。
- 将已有的真实端点断言从并行回归移到现有生产 STDIO 阶段,调用编译后的生产 resolver,并将实际耗时写入 check 报告。生产超时、版本与管道断言不变;失败仍阻断 check,无跳过或自动重试。其余九项 Tray 协议测试保留。Node 官方文档说明测试文件默认通过子进程并行运行:https://nodejs.org/download/release/v22.23.2/docs/api/test.html;本调整减少测试启动竞争,不声称已经确定所有端点超时的根因。
- 本地第一次完整 check 失败于原有目录树断言:worktree 的 .git 是文件,现有目录过滤没有排除它。Git 官方说明此布局:https://git-scm.com/docs/git-worktree。新增一个临时文件回归先复现失败,再仅补充 .git 文件的默认排除;includeIgnored=true 仍能列出该文件。相关浏览测试 17/17 通过。
- 最终一次本地完整 check 通过:Node 24.19.0,454/454、无跳过;生产 STDIO 的 Tray 端点 251.4701 ms、17 工具与导航/EOF 检查通过;三组件正式构建和交付清单验证通过。报告 test-tmp/check/2026-09-11T09-42-37-700Z-core/report.json。先前失败报告 test-tmp/check/2026-09-11T09-37-13-728Z-core/report.json 保留。未再次运行无关 desktop 集合。
- 本次修改不增加依赖或架构层次。远端最终 PR-head 和合并后 main 检查仍待运行;当前 Codex 连接已验证 PR #40 的导航与 UI 行为,但最后这条目录过滤需要下一次正常连接加载新构建。
2 changes: 1 addition & 1 deletion scripts/check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ if (inventoryOnly) {
await node('regression', [tsx, '--test', ...testReporters(directory, 'regression'), ...groups.test]);
const stdio = JSON.parse(await node('stdio', [tsx, 'scripts/test-mcp-client.ts']));
report.runtime = { build: stdio.runtime?.build, schemaHash: stdio.schemaHash, toolCount: stdio.toolCount,
resourceCleanup: stdio.resourceCleanup, codexConnectionVerified: false };
trayEndpoint: stdio.trayEndpoint, resourceCleanup: stdio.resourceCleanup, codexConnectionVerified: false };
await node('delivery-manifest', ['scripts/delivery-manifest.mjs']);
report.delivery = JSON.parse(await node('verify-delivery', ['scripts/delivery-manifest.mjs', '--verify']));
}
Expand Down
9 changes: 7 additions & 2 deletions scripts/test-mcp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ async function verify() {
const client = new Client({ name: 'wincode-runtime-probe', version: '1' });
let transport: StdioClientTransport | undefined;
try {
// check.mjs runs this after parallel regression, so native startup does not compete with its test workers.
const { resolveTrayEndpoint } = await import(pathToFileURL(path.join(repo, 'dist/Gateway/TrayClient.js')).href);
const endpointStarted = performance.now();
assert.match(await resolveTrayEndpoint(new AbortController().signal), /^WinCode\.Tray\.v1\.S-1-/);
const trayEndpoint = { verified: true, durationMs: performance.now() - endpointStarted };
const lines = Array.from({ length: 60 }, (_, index) => index === 49 ?
'export function RuntimeProbeTarget() { return "RUNTIME_TARGET_BODY"; }' : `// padding ${index + 1}`);
await fs.writeFile(path.join(root, 'Target.ts'), lines.join('\n'));
Expand Down Expand Up @@ -80,9 +85,9 @@ await server.start();
assert.deepEqual(after.runtime, hello.runtime);
console.log(JSON.stringify({ status: 'passed', transport: 'stdio', productionHandlers: true,
upstreams: false, gui: false, codexConnectionVerified: false, version: hello.version,
runtime: hello.runtime, schemaHash: hello.toolContract.schemaHash, toolCount: tools.length,
runtime: hello.runtime, schemaHash: hello.toolContract.schemaHash, toolCount: tools.length, trayEndpoint,
resourceCleanup: { observation: 'before shutdown; not proof of process exit', value: hello.health?.resourceCleanup ?? null },
checks: ['initialize', 'tools/list', 'hello schema agreement', 'literal search to source', 'file outline', 'EOF correction to source',
checks: ['published Tray endpoint', 'initialize', 'tools/list', 'hello schema agreement', 'literal search to source', 'file outline', 'EOF correction to source',
'symbol body at line 50', 'exact range body', 'unknown fields ignored', 'known field type rejected', 'stable instance'] }, null, 2));
} finally {
try { await client.close(); } finally {
Expand Down
6 changes: 5 additions & 1 deletion src/Core/WorkspaceBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function listDirectory(root: string, options: WorkspaceDirectoryOpt
if (incomplete) { result.scanComplete = false; result.truncated = true; }
};
// An explicit path is a browsing request, including a source subtree inside work/.
// includeIgnored controls filtering of child directories, not access to that path.
// includeIgnored controls child filtering, not access to that path.
const queue = [{ full, depth: 0 }];
while (queue.length && result.visitedEntries < maxEntries) {
checkOperation(operation);
Expand All @@ -96,6 +96,10 @@ export async function listDirectory(root: string, options: WorkspaceDirectoryOpt
const rel = relative(entryFull);
if (entry.isSymbolicLink()) { omit(rel, 'link-not-followed'); continue; }
if (!entry.isDirectory() && !entry.isFile()) { omit(rel, 'unsupported-entry'); continue; }
// Linked worktrees store their Git metadata pointer in a file, not a directory.
if (entry.name === '.git' && entry.isFile() && !result.limits.includeIgnored) {
omit(rel, 'default-ignore', false); continue;
}
if (entry.isDirectory() && !result.limits.includeIgnored) {
const reason = await previewOmission(entryFull);
if (reason) { omit(rel, reason, false); continue; }
Expand Down
6 changes: 1 addition & 5 deletions tests/tray-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import net from 'node:net';
import { randomUUID } from 'node:crypto';
import { once } from 'node:events';
import { TrayClient, resolveTrayEndpoint } from '../src/Gateway/TrayClient.js';
import { TrayClient } from '../src/Gateway/TrayClient.js';
import { ToolRouter } from '../src/Core/ToolRouter.js';
import { getDefaultConfig } from '../src/Core/Config.js';

Expand Down Expand Up @@ -34,10 +34,6 @@ async function fixture() {
close: async () => { client.dispose(); socket.destroy(); await new Promise<void>(resolve => server.close(() => resolve())); await router.dispose(); } };
}

it('Tray endpoint resolves the published current-user helper without starting a Tray', { timeout: 8000 }, async () => {
assert.match(await resolveTrayEndpoint(new AbortController().signal), /^WinCode\.Tray\.v1\.S-1-/);
});

it('registration acknowledgement is passive and refusal reports its reason without stopping MCP', { timeout: 8000 }, async () => {
const f = await fixture();
try {
Expand Down
11 changes: 11 additions & 0 deletions tests/workspace-files.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ describe('workspace-files', () => {
assert.ok(!childNames.includes('.git'), '.git directory must be excluded');
});

it('excludes worktree git files by default while preserving explicit ignored listings', async () => {
const fixture = await fs.mkdtemp(path.join(testCacheDir, 'git-file-'));
await fs.writeFile(path.join(fixture, '.git'), 'gitdir: ../repository/.git/worktrees/example\n');
await fs.writeFile(path.join(fixture, 'source.ts'), 'export const value = 1;\n');
const workspace = new WorkspaceManager(getDefaultConfig(fixture));
const tree = await workspace.getDirectoryTree(1);
assert.deepStrictEqual(tree.children?.map(item => item.name), ['source.ts']);
const listing = await workspace.listDirectory({ includeIgnored: true, maxDepth: 1 });
assert.ok(listing.entries.some(item => item.path === '.git' && item.type === 'file'));
});

it('Security Boundary: Safe moveToTrash must move file and write metadata', async () => {
const testFile = path.join(root, 'tdd_temp_file_for_trash.txt');
await fs.writeFile(testFile, 'Crucial content that should never be permanently deleted', 'utf-8');
Expand Down