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
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Go build IDs and embedded contract bytes must survive Windows checkouts unchanged.
/native/**/*.go text eol=lf
/native/**/go.mod text eol=lf
/native/**/go.sum text eol=lf
/mcp/contracts-v1.json text eol=lf
6 changes: 3 additions & 3 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v4
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
1 change: 1 addition & 0 deletions .github/workflows/windows-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
- run: npm run verify:desktop-session
- run: npm run verify:backend-integrity
- run: npm run verify:backend-round-two
- run: npm run verify:backend-operation-wait
- run: npm run verify:mcp-reliability
- run: npm run verify:mcp-lifecycle
- run: npm run verify:mcp-policy-regression
Expand Down
11 changes: 9 additions & 2 deletions PRODUCT_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,27 @@ The follow-up reviewed the desktop process and preload, renderer actions and asy
| Terminal interaction | Bound early events, close exited message ports, avoid repeated paste on keyup, and serialize restart/disposal so reopening cannot collide with a pending terminal | `verify:renderer-async`, `verify:desktop-lifecycle`, `verify:terminal-native-lifecycle` |
| Asynchronous renderer work | Keep stale searches, flat listings, comparisons, duplicates, previews and tab loads from replacing newer state; bind Apply to the submitted dialog and payload | `verify:renderer-async`, `verify:renderer-state` |
| Keyboard and model UX | Preserve operation-control focus, label dialogs for assistive technology, and compute indexed STEP normals after installing the index | `verify:renderer-async`, `verify:interaction-quality`, `verify:model-preview-ui` |
| Filesystem integrity | Recheck the entire snapshot after traversal, and make legacy text Undo preserve later files while refusing directory replacement | `verify:backend-round-two`, `verify:backend-integrity` |
| Filesystem integrity | Recheck the entire snapshot and directory membership after traversal, including unchanged Windows timestamps; make legacy text Undo preserve later files while refusing directory replacement | `verify:backend-round-two`, `verify:backend-integrity` |
| Search completeness | Decode bounded UTF-16 content, honor substring matches at token edges, retain fallback candidates for truncated vocabularies, and handle prototype-like names | `verify:backend-round-two`, `verify:folder-index-token-search`, `verify:background-index-token-search` |
| Background index safety and freshness | Exclude followed links, mark exhausted content budgets incomplete, rebuild old stores, and count hidden entries correctly for freshness | `verify:backend-round-two`, background-index corruption/freshness/watch suites |
| Native filesystem helper | Preserve FILETIME dates outside the nanosecond range, cancel work on stdin close, and wait for all active or retiring helper processes before shutdown succeeds | `go test ./...` in `native/fshelper`, `verify:desktop-lifecycle` |
| MCP persisted state and policy | Retain analysis policy bindings, propagate inspection cancellation, reauthorize after UI waits, and resolve retargeted allowed roots per request | `verify:mcp-lifecycle`, `verify:mcp-policy-regression` |
| MCP transport and configuration | Clean up failed startup and staged files, bound individual frames and subscriptions, order acknowledged subscription changes with reconnect replay, and preserve profiles on configuration I/O failure | `verify:mcp-lifecycle`, `verify:mcp-reliability`, native protocol tests |
| Operation completion waits | Register the listener before the initial state read, preserve cancellation, and return current authorized progress after a timeout | `verify:backend-operation-wait`, `verify:mcp-lifecycle`, `verify:mcp-operation-wait` |
| Landing-page feedback | Report failed clipboard fallback truthfully and restore keyboard focus | `verify:landing-page` (mocked clipboard) |
| Dependency maintenance | Pin the compatible YAML parser to 4.3.2 for the newly published [GHSA-2883-xcg3-v3hh advisory](https://github.com/advisories/GHSA-2883-xcg3-v3hh) | `npm audit`, release-feed and packaging checks |

Original-source controls reproduced the desktop lifecycle, renderer ordering, legacy Undo, hidden-index freshness, configuration, subscription, updater, clipboard, and terminal restart failures before their fixes. The native EOF regression also failed before cancellation was added. The updater tests exercise the installed updater's lifecycle with installer launch mocked; the real Electron session test exercises native Command Prompt input/output and a canceled close without touching user data.

An independent differential search check tested 2,989 substring queries over 2,652 entries, including saturated postings and incomplete vocabularies: all 736,804 matching candidates were retained. The 100,000-entry browser check passed three desktop runs with median first paint of 136.7 ms, full hydration of 700.1 ms, 47 mounted file rows, and a 116.1 ms compact warm listing response. The final MCP binary measured 1.7 ms warm p95 with 15.6 MB sidecar RSS. These are local fixture measurements, not universal performance guarantees.

The final source review found no further actionable issues. Focused regressions pass: 34 desktop lifecycle checks, 8 real Electron session checks, 15 backend follow-up and 20 integrity checks, 17 renderer async and 25 state checks, and 15 MCP lifecycle checks. Existing interaction (34), model preview (15), landing page (50), SEO (265), native terminal lifecycle (24 actual exits), native filesystem/protocol tests, real MCP context/operation waits, and 19 release metadata fixtures also pass. The dependency audit reports zero known vulnerabilities at verification time.
The final source review found no further actionable issues. Focused regressions pass: 34 desktop lifecycle checks, 8 real Electron session checks, 16 backend follow-up and 20 integrity checks, 3 operation-wait race checks, 17 renderer async and 25 state checks, and 18 MCP lifecycle checks. Existing interaction (34), model preview (15), landing page (50), SEO (265), native terminal lifecycle (24 actual exits), native filesystem/protocol tests, real MCP context/operation waits, and 19 release metadata fixtures also pass. The dependency audit reports zero known vulnerabilities at verification time.

Directory membership is verified with a transient digest of sorted entry names, independently of timestamp resolution. Deterministic fixtures hold directory metadata constant while adding or renaming entries during traversal; both changes are rejected. This adds one directory enumeration per verified directory and keeps the persisted version 1 snapshot schema and digests compatible for unchanged trees. The Pages deployment actions use their maintained Node 24 releases.

A real transaction fixture completes while the operation wait's initial state read is deliberately held. The original listener ordering missed that completion; registering first passes ten repeated runs, and the real Electron operation-wait smoke passes without increasing its 30-second timeout. Timeout feedback now rereads and authorizes current operation state instead of returning an old queued snapshot.

Native builds pin Go 1.25.12 and keep source/module/embedded-contract inputs in LF form. A fresh Windows checkout with `core.autocrlf=true`, a repeated build, and the workspace build produced identical binary hashes. An isolated executable-lock fixture verifies that failed MCP build publication preserves the previous binary and removes its staged candidate.

Version 1 background search stores are withheld until rebuilt under the current link and encoding rules; the UI can temporarily report stale or indexing status. Existing folder token indexes remain readable through conservative full-scan fallback. Native UAC and actual installer execution remain outside the automated tests. Dedicated new suites are registered in Windows CI and `verify:all`.

Expand Down
4 changes: 3 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### File Safety And Recovery

- Revalidate snapshot entries after the complete traversal, and make Undo from older text saves retain later edits while refusing to replace directories.
- Revalidate snapshot entries and directory membership after the complete traversal, including changes with unchanged timestamps. Undo from older text saves retains later edits while refusing to replace directories.
- Preserve UTF-16 content in direct and indexed search, return complete substring matches, skip linked descendants, and rebuild outdated background indexes automatically.
- Avoid repeated background rebuilds when hidden files are excluded, and report exhausted content budgets accurately.
- Save text through a staged replacement while preserving UTF-8, UTF-16LE, UTF-16BE and their byte-order marks. Undo restores the original bytes; unsupported encodings are rejected before writing.
Expand All @@ -30,6 +30,7 @@
### AI Bridge Reliability

- Preserve retained analysis policy bindings, cancel recursive inspection promptly, and recheck permissions after UI waits and allowed-root changes.
- Observe operation completion even when it happens during the initial status read, and show current authorized progress when a wait times out.
- Bound resource subscriptions and individual transport frames, serialize subscription state, and clean up interrupted bridge startup and staged files.
- Preserve existing profiles when configuration reads fail, and require successful backup before replacing malformed configuration.
- Recheck current permissions when applying previews, controlling operations, reading retained results, and dispatching actions against implicit UI targets. Permission changes invalidate old previews; collection mutations check existing contents too.
Expand All @@ -49,6 +50,7 @@

- Cancel native filesystem work when its parent closes the transport, and preserve Windows timestamps outside the nanosecond conversion range.
- Update the compatible YAML parser pin to 4.3.2 and correct landing-page clipboard failure feedback and focus restoration.
- Make native binaries reproducible across Windows checkouts, preserve prior binaries on failed build publication, and update Pages deployment actions to Node 24.
- Updated vulnerable dependency patches, including the pinned YAML parser, and excluded local editor state and backup files from version control.
- Preserves path edits during pane refreshes and keeps drive buttons at a readable minimum width.

Expand Down
16 changes: 14 additions & 2 deletions filesystem-integrity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export function sameFileIdentity(left, right) {
return Boolean(left && right && String(left.dev) === String(right.dev) && String(left.ino) === String(right.ino));
}

function directoryMembershipDigest(names) {
const hash = crypto.createHash("sha256");
for (const name of names.sort()) hash.update(JSON.stringify(name) + "\n");
return hash.digest("hex");
}

export async function pathSnapshot(target, { signal } = {}) {
const content = crypto.createHash("sha256");
const state = crypto.createHash("sha256");
Expand All @@ -36,26 +42,32 @@ export async function pathSnapshot(target, { signal } = {}) {
} else {
throw new Error(`Unsupported file type in transaction: ${itemPath}`);
}
observations.push({ itemPath, identity, mode: before.mode, size: before.size, mtimeMs: before.mtimeMs, ctimeMs: before.ctimeMs });
const observation = { itemPath, identity, mode: before.mode, size: before.size, mtimeMs: before.mtimeMs, ctimeMs: before.ctimeMs };
observations.push(observation);
entries += 1;
content.update(JSON.stringify([relative, kind, value]) + "\n");
state.update(JSON.stringify([relative, kind, identity, before.mode, before.size, before.mtimeMs, value]) + "\n");
if (kind === "directory") {
const names = await fs.readdir(itemPath);
names.sort();
observation.directoryDigest = directoryMembershipDigest(names);
for (const name of names) await visit(path.join(itemPath, name), relative ? `${relative}/${name}` : name);
}
}
await visit(path.resolve(target), "");
// Recheck after the entire traversal: an earlier file can change while a
// sibling is hashed, and a directory can gain children after readdir.
// Compare membership too: Windows may not advance directory timestamps for
// same-tick changes. Keep this transient digest out of persisted v1 snapshots.
for (const before of observations) {
throwIfAborted(signal);
const after = await fs.lstat(before.itemPath);
if (!sameFileIdentity(before.identity, after) || before.size !== after.size || before.mtimeMs !== after.mtimeMs ||
before.ctimeMs !== after.ctimeMs || before.mode !== after.mode) {
throw new Error(`File changed while verifying transaction: ${before.itemPath}`);
}
if (before.directoryDigest && before.directoryDigest !== directoryMembershipDigest(await fs.readdir(before.itemPath))) {
throw new Error(`File changed while verifying transaction: ${before.itemPath}`);
}
}
throwIfAborted(signal);
return { version: 1, identity: rootIdentity, entries, contentDigest: content.digest("hex"), stateDigest: state.digest("hex") };
Expand Down
13 changes: 9 additions & 4 deletions mcp/automation-service.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1184,15 +1184,20 @@ export async function createMcpAutomationService(deps) {
else request.signal?.addEventListener?.("abort", onAbort, { once: true });
});
const [data, waitedOperation] = await Promise.all([uiWait, operationWait]);
if (waitedOperation) operation = waitedOperation;
const latest = await uiDispatcher({ type: "wait", afterRevision: 0, timeoutMs: 100, condition: {}, signal: request.signal });
const operationMatched = !operationId || Boolean(waitedOperation && (!operationStatus || waitedOperation.status === operationStatus));
const [latest, currentOperation] = await Promise.all([
uiDispatcher({ type: "wait", afterRevision: 0, timeoutMs: 100, condition: {}, signal: request.signal }),
operationId ? deps.getOperation(operationId) : null
]);
// Report current progress even after a timeout, while preserving whether
// the wait itself observed the requested condition.
operation = currentOperation;
const fresh = await currentPrincipal(principal);
if (operation) {
try { await authorizeOperation(fresh, operation); }
catch { operation = null; }
}
const operationMatched = !operationId || Boolean(operation && (!operationStatus || operation.status === operationStatus));
const matched = data?.matched === true && operationMatched;
const matched = data?.matched === true && operationMatched && (!operationId || Boolean(operation));
const authorized = await contextForPrincipal(fresh, latest.context || data.context || principal.context);
return resultEnvelope({
matched,
Expand Down
Binary file modified native/bin/explore-better-fs.exe
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
"verify:mcp-client-roots": "node scripts/mcp-client-roots-smoke.mjs",
"verify:backend-integrity": "node scripts/backend-integrity-smoke.mjs",
"verify:backend-round-two": "node scripts/backend-round-two-smoke.mjs",
"verify:backend-operation-wait": "node scripts/backend-operation-wait-smoke.mjs",
"verify:renderer-state": "node scripts/renderer-state-regression-smoke.mjs",
"verify:renderer-async": "node scripts/renderer-async-regression-smoke.mjs",
"verify:clipboard-sequence": "node scripts/clipboard-sequence-smoke.mjs",
Expand Down
Loading