deadbyte-mcp 0.15.3 → 0.15.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.
@@ -18,7 +18,7 @@ export async function verifyFinalClosureObject(envelope,{root=packageRoot,runtim
18
18
  const candidate=path.resolve(root),state=path.resolve(runtimeStateRoot);
19
19
  insist(envelope&&typeof envelope==='object'&&!Array.isArray(envelope),'R46 closure envelope invalid');
20
20
  insist(envelope.body?.schema==='deadbyte.r46-final-closure.v1','R46 closure schema mismatch');
21
- insist(envelope.body?.version==='0.15.2'&&envelope.body?.train==='R46','R46 closure version/train mismatch');
21
+ insist(envelope.body?.version==='0.15.5'&&envelope.body?.train==='R46','R46 closure version/train mismatch');
22
22
  insist(envelope.body?.release_label==='FILELESS POWERSHELL PRODUCT BASELINE','R46 closure label mismatch');
23
23
 
24
24
  const bodyBytes=Buffer.from(canonicalJson(envelope.body),'utf8');
@@ -39,8 +39,8 @@ export async function verifyFinalClosureObject(envelope,{root=packageRoot,runtim
39
39
  const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
40
40
  const manifestSha256=sha256(manifestBytes);
41
41
  const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
42
- insist(pointer.version==='0.15.2'&&pointer.manifest_sha256===manifestSha256,'R46 active pointer/candidate mismatch');
43
- insist(pointer.release_id===`v0.15.2-${manifestSha256.slice(0,16)}`,'R46 active release id mismatch');
42
+ insist(pointer.version==='0.15.5'&&pointer.manifest_sha256===manifestSha256,'R46 active pointer/candidate mismatch');
43
+ insist(pointer.release_id===`v0.15.5-${manifestSha256.slice(0,16)}`,'R46 active release id mismatch');
44
44
  insist(envelope.body.active_pointer?.release_id===pointer.release_id&&
45
45
  envelope.body.active_pointer?.manifest_sha256===pointer.manifest_sha256&&
46
46
  envelope.body.active_pointer?.generation_manifest_sha256===pointer.generation_manifest_sha256,
@@ -55,12 +55,12 @@ export async function verifyFinalClosureObject(envelope,{root=packageRoot,runtim
55
55
  }
56
56
  const gate=JSON.parse(await readFile(path.resolve(gateRef.path),'utf8'));
57
57
  const parity=JSON.parse(await readFile(path.resolve(parityRef.path),'utf8'));
58
- insist(gate.schema==='deadbyte.r46-windows-gate.v1'&&gate.status==='passed'&&gate.version==='0.15.2','R46 gate evidence invalid');
58
+ insist(gate.schema==='deadbyte.r46-windows-gate.v1'&&gate.status==='passed'&&gate.version==='0.15.5','R46 gate evidence invalid');
59
59
  insist(gate.manifest_sha256===manifestSha256,'R46 gate candidate mismatch');
60
60
  insist(gate.parent?.release_id==='v0.15.1-d55f00b5144dc31a'&&gate.parent?.manifest_sha256==='d55f00b5144dc31a7fe39e4dc60d640a94780da6aaeca0918e87636b816d7854','R46 sealed parent identity invalid');
61
- insist(gate.runtime_predecessor?.release_id==='v0.15.2-0fb46542f6a4345f'&&gate.runtime_predecessor?.manifest_sha256==='0fb46542f6a4345f1c527565d06bf6e9b8e283260e47bf684fb70a60308fcf34','R46 reopened runtime predecessor identity invalid');
61
+ insist(gate.runtime_predecessor?.release_id==='v0.15.5-a03a4b2cf28d31d6'&&gate.runtime_predecessor?.manifest_sha256==='a03a4b2cf28d31d633cfbb0a073d375c0f19d1b972299faacef28eec05543995','R46 reopened runtime predecessor identity invalid');
62
62
  insist(envelope.body.runtime_predecessor?.release_id===gate.runtime_predecessor.release_id&&envelope.body.runtime_predecessor?.manifest_sha256===gate.runtime_predecessor.manifest_sha256,'R46 closure runtime predecessor mismatch');
63
- insist(parity.schema==='deadbyte.r46-parity.v1'&&parity.status==='passed'&&parity.version==='0.15.2','R46 parity evidence invalid');
63
+ insist(parity.schema==='deadbyte.r46-parity.v1'&&parity.status==='passed'&&parity.version==='0.15.5','R46 parity evidence invalid');
64
64
  insist(parity.candidate?.release_id===pointer.release_id&&
65
65
  parity.candidate?.manifest_sha256===pointer.manifest_sha256&&
66
66
  parity.candidate?.generation_manifest_sha256===pointer.generation_manifest_sha256,
@@ -94,7 +94,7 @@ export async function verifyFinalClosureObject(envelope,{root=packageRoot,runtim
94
94
  'R46 closure retained-output ring proof invalid');
95
95
 
96
96
  return {
97
- ok:true,status:'passed',version:'0.15.2',train:'R46',
97
+ ok:true,status:'passed',version:'0.15.5',train:'R46',
98
98
  release_id:pointer.release_id,
99
99
  manifest_sha256:pointer.manifest_sha256,
100
100
  generation_manifest_sha256:pointer.generation_manifest_sha256,
@@ -10,7 +10,7 @@ function Invoke-GateStep {
10
10
  $LASTEXITCODE = 0
11
11
  & npm.cmd @NpmArgs
12
12
  if ($LASTEXITCODE -ne 0) {
13
- throw "DEADBYTE V0.15.2 / R46 gate failed at $Name with exit code $LASTEXITCODE"
13
+ throw "DEADBYTE V0.15.5 / R46 gate failed at $Name with exit code $LASTEXITCODE"
14
14
  }
15
15
  }
16
16
 
@@ -23,7 +23,7 @@ Write-Host '==== host-policy-hermetic-clean ===='
23
23
  $HostPolicyPath = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\controller\deadbyte-host-policy.json'))
24
24
  Remove-Item -LiteralPath $HostPolicyPath -Force -ErrorAction SilentlyContinue
25
25
  if (Test-Path -LiteralPath $HostPolicyPath) {
26
- throw "DEADBYTE V0.15.2 / R46 gate could not remove machine-local Host policy before hermetic regression"
26
+ throw "DEADBYTE V0.15.5 / R46 gate could not remove machine-local Host policy before hermetic regression"
27
27
  }
28
28
  Write-Host 'HOST_POLICY_HERMETIC_ABSENT'
29
29
 
@@ -53,6 +53,6 @@ Write-Host '==== windows-gate-evidence ===='
53
53
  $LASTEXITCODE = 0
54
54
  & node.exe (Join-Path $PSScriptRoot 'windows-gate-evidence-r46.mjs')
55
55
  if ($LASTEXITCODE -ne 0) {
56
- throw "DEADBYTE V0.15.2 / R46 gate failed at windows-gate-evidence with exit code $LASTEXITCODE"
56
+ throw "DEADBYTE V0.15.5 / R46 gate failed at windows-gate-evidence with exit code $LASTEXITCODE"
57
57
  }
58
- Write-Host '==== DEADBYTE V0.15.2 / R46 FILELESS POWERSHELL WINDOWS BATCH GATE: PASS ===='
58
+ Write-Host '==== DEADBYTE V0.15.5 / R46 FILELESS POWERSHELL WINDOWS BATCH GATE: PASS ===='
@@ -37,11 +37,11 @@ export async function runReleaseParity({
37
37
  const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
38
38
  const manifestSha256=sha256(manifestBytes);
39
39
  const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
40
- insist(pkg.version==='0.15.2','R46 parity requires package version 0.15.2');
40
+ insist(pkg.version==='0.15.5','R46 parity requires package version 0.15.5');
41
41
 
42
42
  const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
43
- insist(pointer.version==='0.15.2'&&pointer.manifest_sha256===manifestSha256,'R46 active pointer candidate mismatch');
44
- insist(pointer.release_id===`v0.15.2-${manifestSha256.slice(0,16)}`,'R46 active release id mismatch');
43
+ insist(pointer.version==='0.15.5'&&pointer.manifest_sha256===manifestSha256,'R46 active pointer candidate mismatch');
44
+ insist(pointer.release_id===`v0.15.5-${manifestSha256.slice(0,16)}`,'R46 active release id mismatch');
45
45
  const releaseManifest=await readFile(path.join(pointer.release_root,'MANIFEST.SHA256'));
46
46
  insist(sha256(releaseManifest)===manifestSha256,'R46 immutable release manifest mismatch');
47
47
 
@@ -116,7 +116,7 @@ export async function runReleaseParity({
116
116
  const evidence={
117
117
  schema:'deadbyte.r46-parity.v1',
118
118
  status:'passed',
119
- version:'0.15.2',
119
+ version:'0.15.5',
120
120
  completed_at_utc:completed.toISOString(),
121
121
  candidate:{
122
122
  release_id:pointer.release_id,
@@ -8,7 +8,7 @@ import { doctor } from '../src/doctor-r45.mjs';
8
8
  const here=path.dirname(fileURLToPath(import.meta.url));
9
9
  const root=path.resolve(here,'..');
10
10
  const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
11
- const marker='DEADBYTE V0.15.2 / R46 FILELESS POWERSHELL WINDOWS BATCH GATE: PASS';
11
+ const marker='DEADBYTE V0.15.5 / R46 FILELESS POWERSHELL WINDOWS BATCH GATE: PASS';
12
12
  const PARENT_RELEASE_ID='v0.15.1-d55f00b5144dc31a';
13
13
  const PARENT_MANIFEST='d55f00b5144dc31a7fe39e4dc60d640a94780da6aaeca0918e87636b816d7854';
14
14
  const PARENT_GENERATION='00cde7bf540cc2804d679a39040d5b07193c5f8a0d49cac3b882402f8f4e6ad8';
@@ -26,18 +26,18 @@ export async function writeWindowsGateEvidence({
26
26
  const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
27
27
  const manifestSha256=sha256(manifestBytes);
28
28
  const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
29
- insist(pkg.version==='0.15.2',`R46 Windows gate evidence requires V0.15.2, found ${pkg.version}`);
29
+ insist(pkg.version==='0.15.5',`R46 Windows gate evidence requires V0.15.5, found ${pkg.version}`);
30
30
  const release=JSON.parse(await readFile(path.join(candidate,'RELEASE.json'),'utf8'));
31
- insist(release.version==='0.15.2'&&release.train==='R46','R46 release metadata mismatch');
31
+ insist(release.version==='0.15.5'&&release.train==='R46','R46 release metadata mismatch');
32
32
  const active=JSON.parse(await readFile(path.join(path.resolve(stateRoot),'active-release.json'),'utf8'));
33
33
  const originalParent=active.release_id===PARENT_RELEASE_ID&&active.manifest_sha256===PARENT_MANIFEST&&active.generation_manifest_sha256===PARENT_GENERATION;
34
34
  const reopenedPredecessor=active.release_id===REOPENED_RUNTIME_RELEASE_ID&&active.manifest_sha256===REOPENED_RUNTIME_MANIFEST&&active.generation_manifest_sha256===REOPENED_RUNTIME_GENERATION;
35
- const currentR46Predecessor=active.version==='0.15.2'&&active.manifest_sha256!==manifestSha256&&/^[0-9a-f]{64}$/.test(active.generation_manifest_sha256??'');
35
+ const currentR46Predecessor=active.version==='0.15.5'&&active.manifest_sha256!==manifestSha256&&/^[0-9a-f]{64}$/.test(active.generation_manifest_sha256??'');
36
36
  insist(originalParent||reopenedPredecessor||currentR46Predecessor,'R46 runtime predecessor identity mismatch');
37
37
  const parent={release_id:PARENT_RELEASE_ID,version:'0.15.1',manifest_sha256:PARENT_MANIFEST,generation_manifest_sha256:PARENT_GENERATION};
38
38
 
39
39
  const doctorResult=await doctor();
40
- insist(doctorResult?.status==='ok'&&doctorResult?.package_version==='0.15.2'&&doctorResult?.manifest?.verified===true,'R46 doctor evidence invalid');
40
+ insist(doctorResult?.status==='ok'&&doctorResult?.package_version==='0.15.5'&&doctorResult?.manifest?.verified===true,'R46 doctor evidence invalid');
41
41
  insist(doctorResult?.policy?.host_v2_ready===true,'R46 doctor did not verify Host policy v2');
42
42
 
43
43
  const completed=clock();
@@ -46,7 +46,7 @@ export async function writeWindowsGateEvidence({
46
46
  schema:'deadbyte.r46-windows-gate.v1',
47
47
  status:'passed',
48
48
  exit_code:0,
49
- version:'0.15.2',
49
+ version:'0.15.5',
50
50
  train:'R46',
51
51
  marker,
52
52
  completed_at_utc:completed.toISOString(),
@@ -100,7 +100,7 @@ export async function writeWindowsGateEvidence({
100
100
  const existingBytes=await readFile(file);
101
101
  const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
102
102
  insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing R46 Windows gate evidence invalid');
103
- insist(existing.version==='0.15.2'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing R46 Windows gate identity mismatch');
103
+ insist(existing.version==='0.15.5'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing R46 Windows gate identity mismatch');
104
104
  insist(existing.parent?.release_id===parent.release_id&&existing.parent?.manifest_sha256===parent.manifest_sha256,'existing R46 parent evidence mismatch');
105
105
  insist(existing.runtime_predecessor?.release_id===active.release_id&&existing.runtime_predecessor?.manifest_sha256===active.manifest_sha256,'existing R46 runtime predecessor evidence mismatch');
106
106
  return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256,parent:existing.parent};
@@ -70,14 +70,14 @@ export function registerCodingTools(server, { codingRuntime, taskRuntime, semant
70
70
  server.registerTool('coding_search', {
71
71
  description: 'Deterministic bounded content/path search with literal or regex mode, signed cursor paging, and observed file identities.',
72
72
  inputSchema: z.strictObject({ root_id: rootId, path: relPath.default('.'), pattern: z.string().min(1).max(256),
73
- target: z.enum(['content','path']).default('content'), mode: z.enum(['literal','regex']).default('literal'), case_sensitive: z.boolean().default(false),
73
+ target: z.enum(['content','path']).default('content'), mode: z.enum(['literal','regex','fuzzy']).default('literal'), case_sensitive: z.boolean().default(false),
74
74
  max_results: z.number().int().min(1).max(1000).default(200), max_file_bytes: z.number().int().min(1).max(4194304).default(262144),
75
75
  max_scan_bytes: z.number().int().min(1).max(67108864).default(4194304), page_size: z.number().int().min(1).max(500).default(100),
76
- cursor: z.string().max(8192).nullable().default(null) }),
76
+ cursor: z.string().max(8192).nullable().default(null), fuzzy_timeout_ms: z.number().int().min(100).max(30000).default(30000) }),
77
77
  outputSchema: codingSearchOutputSchema
78
- }, async ({ root_id, path, pattern, target, mode, case_sensitive, max_results, max_file_bytes, max_scan_bytes, page_size, cursor }) => {
78
+ }, async ({ root_id, path, pattern, target, mode, case_sensitive, max_results, max_file_bytes, max_scan_bytes, page_size, cursor, fuzzy_timeout_ms }) => {
79
79
  try { return result(codingSearchOutputSchema, await codingRuntime.search({ rootId: root_id, path, pattern, target, mode, caseSensitive: case_sensitive,
80
- maxResults: max_results, maxFileBytes: max_file_bytes, maxScanBytes: max_scan_bytes, pageSize: page_size, cursor })); }
80
+ maxResults: max_results, maxFileBytes: max_file_bytes, maxScanBytes: max_scan_bytes, pageSize: page_size, cursor, fuzzyTimeoutMs: fuzzy_timeout_ms })); }
81
81
  catch (error) { return fail(codingSearchOutputSchema, error); }
82
82
  });
83
83
 
@@ -92,7 +92,8 @@ export const codingSearchOutputSchema = statusContract({
92
92
  pattern: z.string().min(1).max(256).optional(), target: z.enum(['content','path']).optional(),
93
93
  mode: z.enum(['literal','regex']).optional(), case_sensitive: z.boolean().optional(),
94
94
  matches: z.array(z.strictObject({ path: z.string().min(1).max(512), line: z.number().int().min(1).nullable(),
95
- column: z.number().int().min(1).nullable(), preview: z.string().max(512), file_sha256: hex64.nullable() })).max(500).optional(),
95
+ column: z.number().int().min(1).nullable(), preview: z.string().max(512), file_sha256: hex64.nullable(),
96
+ distance: z.number().int().nonnegative().optional(), similarity: z.number().min(0).max(1).optional(), match_length: z.number().int().positive().optional() })).max(500).optional(),
96
97
  files_scanned: z.number().int().min(0).optional(), bytes_scanned: z.number().int().min(0).optional(),
97
98
  binary_files_skipped: z.number().int().min(0).optional(), truncated: z.boolean().optional(),
98
99
  truncation_reason: z.enum(['tree_limit','scan_byte_limit','max_results']).nullable().optional(),
@@ -2,6 +2,8 @@ import { createHash } from 'node:crypto';
2
2
  import { canonicalJson } from './canonical-json.mjs';
3
3
  import { decodeUtf8Strict } from './text-policy.mjs';
4
4
  import { createSequenceStream } from './workload-substrate.mjs';
5
+ import { fuzzyIndexOf } from './fuzzy-search-core.mjs';
6
+ import { runFuzzySearchInWorker, FUZZY_SEARCH_TIMEOUT_MS } from './fuzzy-search.mjs';
5
7
 
6
8
  const sha256Object=value=>createHash('sha256').update(canonicalJson(value),'utf8').digest('hex');
7
9
  const sha256Bytes=bytes=>createHash('sha256').update(bytes).digest('hex');
@@ -53,11 +55,11 @@ function preview(text,index,length){
53
55
  const start=Math.max(0,index-48); const end=Math.min(text.length,index+Math.max(length,1)+112);
54
56
  return text.slice(start,end);
55
57
  }
56
- function normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize}){
58
+ function normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize,fuzzyTimeoutMs}){
57
59
  return {
58
- schema:'deadbyte.coding-search-query.v2',root_id:rootId,path,target,mode,pattern,
60
+ schema:'deadbyte.coding-search-query.v3',root_id:rootId,path,target,mode,pattern,
59
61
  case_sensitive:caseSensitive,max_results:maxResults,max_file_bytes:maxFileBytes,
60
- max_scan_bytes:maxScanBytes,page_size:pageSize
62
+ max_scan_bytes:maxScanBytes,page_size:pageSize,fuzzy_timeout_ms:fuzzyTimeoutMs
61
63
  };
62
64
  }
63
65
 
@@ -67,15 +69,18 @@ export function createCodingSearch({tree,fileRead,cursorCodec}){
67
69
  }
68
70
  return async function search({
69
71
  rootId,path='.',pattern,target='content',mode='literal',caseSensitive=false,
70
- maxResults=200,maxFileBytes=262144,maxScanBytes=4*1024*1024,pageSize=100,cursor=null
72
+ maxResults=200,maxFileBytes=262144,maxScanBytes=4*1024*1024,pageSize=100,cursor=null,
73
+ fuzzyTimeoutMs=FUZZY_SEARCH_TIMEOUT_MS
71
74
  }){
72
75
  if(!['content','path'].includes(target)) throw new Error('coding search target must be content or path');
76
+ if(!['literal','regex','fuzzy'].includes(mode)) throw new Error('coding search mode must be literal, regex, or fuzzy');
73
77
  validateInt(maxResults,'max_results',1,1000);
74
78
  validateInt(maxFileBytes,'max_file_bytes',1,4*1024*1024);
75
79
  validateInt(maxScanBytes,'max_scan_bytes',1,64*1024*1024);
76
80
  validateInt(pageSize,'page_size',1,500);
77
- const matcher=compileMatcher({pattern,mode,caseSensitive});
78
- const query=normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize});
81
+ if(!Number.isInteger(fuzzyTimeoutMs)||fuzzyTimeoutMs<100||fuzzyTimeoutMs>FUZZY_SEARCH_TIMEOUT_MS) throw new Error('fuzzy_timeout_ms must be in [100, 30000]');
82
+ const matcher=mode==='fuzzy'?null:compileMatcher({pattern,mode,caseSensitive});
83
+ const query=normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize,fuzzyTimeoutMs});
79
84
  const querySha=sha256Object(query);
80
85
  let decodedCursor=null;
81
86
  if(cursor!==null){
@@ -93,10 +98,18 @@ export function createCodingSearch({tree,fileRead,cursorCodec}){
93
98
  if(target==='path'){
94
99
  for(const entry of entries){
95
100
  snapshotEntries.push({path:entry.path,bytes:entry.bytes,type:'file'});
96
- if(matcher.path(entry.path)){
97
- matches.push({path:entry.path,line:null,column:null,preview:entry.path,file_sha256:null});
101
+ const found=mode==='fuzzy'
102
+ ? [fuzzyIndexOf(caseSensitive?entry.path:entry.path.toLowerCase(),caseSensitive?pattern:pattern.toLowerCase())]
103
+ : (matcher.path(entry.path)?[{start:0,end:entry.path.length,value:entry.path}]:[]);
104
+ for(const hit of found){
105
+ if(mode==='fuzzy' && hit.distance>Math.max(1,Math.floor(pattern.length*0.75))) continue;
106
+ matches.push({
107
+ path:entry.path,line:null,column:null,preview:entry.path,file_sha256:null,
108
+ ...(mode==='fuzzy'?{distance:hit.distance,similarity:hit.similarity,match_length:Math.max(1,hit.value.length)}:{})
109
+ });
98
110
  if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
99
111
  }
112
+ if(matches.length>=maxResults) break;
100
113
  }
101
114
  } else {
102
115
  for(const entry of entries){
@@ -110,11 +123,26 @@ export function createCodingSearch({tree,fileRead,cursorCodec}){
110
123
  snapshotEntries.push({path:entry.path,bytes:item.bytes,sha256:fileSha});
111
124
  let text;
112
125
  try{text=decodeUtf8Strict(raw);}catch{binaryFilesSkipped+=1;continue;}
113
- const found=matcher.all(text);
114
- for(const hit of found){
115
- const lc=lineColumn(text,hit.index);
116
- matches.push({path:entry.path,line:lc.line,column:lc.column,preview:preview(text,hit.index,hit.length),file_sha256:fileSha});
117
- if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
126
+ if(mode==='fuzzy'){
127
+ const haystack=caseSensitive?text:text.toLowerCase();
128
+ const queryText=caseSensitive?pattern:pattern.toLowerCase();
129
+ const hit=await runFuzzySearchInWorker(haystack,queryText,fuzzyTimeoutMs);
130
+ const threshold=Math.max(1,Math.floor(queryText.length*0.75));
131
+ if(hit.distance<=threshold){
132
+ const lc=lineColumn(haystack,hit.start);
133
+ matches.push({
134
+ path:entry.path,line:lc.line,column:lc.column,
135
+ preview:preview(text,hit.start,hit.end-hit.start),file_sha256:fileSha,
136
+ distance:hit.distance,similarity:hit.similarity,match_length:Math.max(1,hit.value.length)
137
+ });
138
+ }
139
+ } else {
140
+ const found=matcher.all(text);
141
+ for(const hit of found){
142
+ const lc=lineColumn(text,hit.index);
143
+ matches.push({path:entry.path,line:lc.line,column:lc.column,preview:preview(text,hit.index,hit.length),file_sha256:fileSha});
144
+ if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
145
+ }
118
146
  }
119
147
  if(matches.length>=maxResults) break;
120
148
  }
@@ -13,7 +13,7 @@ const symbolName=z.string().regex(/^[A-Za-z_$][A-Za-z0-9_$]{0,127}$/);
13
13
 
14
14
  export const codingInspectInputSchema=z.discriminatedUnion('op',[
15
15
  z.strictObject({op:z.literal('stat'),root_id:id,path:rel512.default('.'),hash:z.boolean().default(false)}),
16
- z.strictObject({op:z.literal('search'),root_id:id,path:rel512.default('.'),pattern:z.string().min(1).max(256),target:z.enum(['content','path']).default('content'),mode:z.enum(['literal','regex']).default('literal'),case_sensitive:z.boolean().default(false),max_results:z.number().int().min(1).max(1000).default(200),max_file_bytes:z.number().int().min(1).max(4*1024*1024).default(262144),max_scan_bytes:z.number().int().min(1).max(64*1024*1024).default(4*1024*1024),page_size:z.number().int().min(1).max(500).default(100),cursor}),
16
+ z.strictObject({op:z.literal('search'),root_id:id,path:rel512.default('.'),pattern:z.string().min(1).max(256),target:z.enum(['content','path']).default('content'),mode:z.enum(['literal','regex','fuzzy']).default('literal'),case_sensitive:z.boolean().default(false),max_results:z.number().int().min(1).max(1000).default(200),max_file_bytes:z.number().int().min(1).max(4*1024*1024).default(262144),max_scan_bytes:z.number().int().min(1).max(64*1024*1024).default(4*1024*1024),page_size:z.number().int().min(1).max(500).default(100),cursor,fuzzy_timeout_ms:z.number().int().min(100).max(30000).default(30000)}),
17
17
  z.strictObject({op:z.literal('files_read'),root_id:id,files:z.array(z.strictObject({path:rel512,encoding:z.enum(['utf8','base64']).default('utf8')})).min(1).max(32),max_total_bytes:z.number().int().min(1).max(2097152).default(1048576)}),
18
18
  z.strictObject({op:z.literal('symbol_outline'),root_id:id,path:rel512,max_symbols:z.number().int().min(1).max(500).default(200)}),
19
19
  z.strictObject({op:z.literal('symbol_definition'),root_id:id,path:rel512,line:nonneg,column:nonneg,max_results:z.number().int().min(1).max(200).default(50)}),
@@ -26,7 +26,7 @@ export const codingInspectInputSchema=z.discriminatedUnion('op',[
26
26
  export const machineFsGatewayInputSchema=z.discriminatedUnion('op',[
27
27
  z.strictObject({op:z.literal('stat'),root_id:id,path:rel.default('.'),hash:z.boolean().default(false)}),
28
28
  z.strictObject({op:z.literal('list'),root_id:id,path:rel.default('.'),cursor,limit:z.number().int().min(1).max(1000).default(100),hash_files:z.boolean().default(false)}),
29
- z.strictObject({op:z.literal('read'),root_id:id,path:rel,offset:nonneg.default(0),max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536),hash_file:z.boolean().default(false),timeout_ms:z.number().int().min(100).max(180000).default(30000)}),
29
+ z.strictObject({op:z.literal('read'),root_id:id,path:rel,offset:z.number().int().min(-4*1024*1024).max(4*1024*1024).default(0),max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536),hash_file:z.boolean().default(false),timeout_ms:z.number().int().min(100).max(180000).default(30000)}),
30
30
  z.strictObject({op:z.literal('write'),root_id:id,path:rel,content:z.string().max(12*1024*1024),encoding:z.enum(['utf8','base64']).default('utf8'),expected_sha256:z.string().regex(/^(?:absent|[0-9a-f]{64})$/)}),
31
31
  z.strictObject({op:z.literal('edit_exact'),root_id:id,path:rel,expected_sha256:hex64,old_string:z.string().min(1).max(262144),new_string:z.string().max(524288),expected_replacements:z.number().int().min(1).max(64).default(1)}),
32
32
  z.strictObject({op:z.literal('mkdir'),root_id:id,path:rel}),
@@ -69,7 +69,7 @@ async function invoke(op,runtime,name,call){
69
69
  async function codingInspect(args,codingRuntime,semanticRuntime){
70
70
  switch(args.op){
71
71
  case 'stat': return invoke(args.op,codingRuntime,'coding',r=>r.stat({rootId:args.root_id,relativePath:args.path,hash:args.hash}));
72
- case 'search': return invoke(args.op,codingRuntime,'coding',r=>r.search({rootId:args.root_id,path:args.path,pattern:args.pattern,target:args.target,mode:args.mode,caseSensitive:args.case_sensitive,maxResults:args.max_results,maxFileBytes:args.max_file_bytes,maxScanBytes:args.max_scan_bytes,pageSize:args.page_size,cursor:args.cursor}));
72
+ case 'search': return invoke(args.op,codingRuntime,'coding',r=>r.search({rootId:args.root_id,path:args.path,pattern:args.pattern,target:args.target,mode:args.mode,caseSensitive:args.case_sensitive,maxResults:args.max_results,maxFileBytes:args.max_file_bytes,maxScanBytes:args.max_scan_bytes,pageSize:args.page_size,cursor:args.cursor,fuzzyTimeoutMs:args.fuzzy_timeout_ms}));
73
73
  case 'files_read': return invoke(args.op,codingRuntime,'coding',r=>r.filesRead({rootId:args.root_id,files:args.files,maxTotalBytes:args.max_total_bytes}));
74
74
  case 'symbol_outline': return invoke(args.op,semanticRuntime,'semantic',r=>r.outline({rootId:args.root_id,relativePath:args.path,maxSymbols:args.max_symbols}));
75
75
  case 'symbol_definition': return invoke(args.op,semanticRuntime,'semantic',r=>r.definition({rootId:args.root_id,relativePath:args.path,line:args.line,column:args.column,maxResults:args.max_results}));
@@ -0,0 +1,117 @@
1
+ const clamp=(value,min,max)=>Math.min(max,Math.max(min,value));
2
+
3
+ function boundedLevenshtein(a,b,cutoff=Number.POSITIVE_INFINITY){
4
+ if(a===b)return 0;
5
+ if(a.length===0)return b.length;
6
+ if(b.length===0)return a.length;
7
+ if(a.length>b.length){const t=a;a=b;b=t;}
8
+ const previous=new Uint16Array(a.length+1);
9
+ const current=new Uint16Array(a.length+1);
10
+ for(let i=0;i<=a.length;i+=1)previous[i]=i;
11
+ const limit=Number.isFinite(cutoff)?Math.floor(cutoff):Number.MAX_SAFE_INTEGER;
12
+ for(let row=1;row<=b.length;row+=1){
13
+ current[0]=row;
14
+ let rowMin=current[0];
15
+ const ch=b.charCodeAt(row-1);
16
+ for(let col=1;col<=a.length;col+=1){
17
+ const cost=a.charCodeAt(col-1)===ch?0:1;
18
+ const value=Math.min(current[col-1]+1,previous[col]+1,previous[col-1]+cost);
19
+ current[col]=value;
20
+ if(value<rowMin)rowMin=value;
21
+ }
22
+ if(rowMin>limit)return limit+1;
23
+ previous.set(current);
24
+ }
25
+ return previous[a.length];
26
+ }
27
+
28
+ function cheapPositionalScore(text,query,start,length){
29
+ const overlap=Math.min(length,query.length);
30
+ if(overlap<=0)return -1;
31
+ let same=0;
32
+ for(let i=0;i<overlap;i+=1)if(text.charCodeAt(start+i)===query.charCodeAt(i))same+=1;
33
+ const lengthPenalty=Math.abs(length-query.length)/Math.max(1,query.length);
34
+ return (same/overlap)-lengthPenalty*0.20;
35
+ }
36
+
37
+ function candidateLengths(queryLength,textLength){
38
+ if(textLength<=queryLength)return [textLength];
39
+ const delta=clamp(Math.floor(queryLength*0.25),2,32);
40
+ return [...new Set([
41
+ Math.max(1,queryLength-delta),
42
+ queryLength,
43
+ Math.min(textLength,queryLength+delta)
44
+ ])];
45
+ }
46
+
47
+ export function fuzzyIndexOf(text,query){
48
+ if(typeof text!=='string'||typeof query!=='string')throw new Error('fuzzy search requires text and query strings');
49
+ if(query.length===0)return {start:0,end:0,value:'',distance:0,similarity:1};
50
+ if(text.length===0)return {start:0,end:0,value:'',distance:query.length,similarity:0};
51
+
52
+ const lengths=candidateLengths(query.length,text.length);
53
+ const stride=query.length<=32?1:Math.max(1,Math.floor(query.length/4));
54
+ const ranked=[];
55
+ const seen=new Set();
56
+ for(let start=0;start<text.length;start+=stride){
57
+ for(const length of lengths){
58
+ if(start+length>text.length)continue;
59
+ const key=`${start}:${length}`;
60
+ if(seen.has(key))continue;
61
+ seen.add(key);
62
+ ranked.push({start,length,score:cheapPositionalScore(text,query,start,length)});
63
+ }
64
+ }
65
+ const tailStart=Math.max(0,text.length-query.length);
66
+ for(const start of [0,tailStart]){
67
+ for(const length of lengths){
68
+ if(start<0||start+length>text.length)continue;
69
+ const key=`${start}:${length}`;
70
+ if(seen.has(key))continue;
71
+ seen.add(key);
72
+ ranked.push({start,length,score:cheapPositionalScore(text,query,start,length)});
73
+ }
74
+ }
75
+
76
+ ranked.sort((a,b)=>b.score-a.score||a.start-b.start||a.length-b.length);
77
+ const shortlist=ranked.slice(0,64);
78
+ const refined=new Set();
79
+ for(const item of shortlist){
80
+ const radius=Math.max(1,Math.floor(stride/2));
81
+ for(let delta=-radius;delta<=radius;delta+=1){
82
+ const start=item.start+delta;
83
+ if(start<0||start>=text.length)continue;
84
+ for(const length of lengths){
85
+ if(start+length>text.length)continue;
86
+ refined.add(`${start}:${length}`);
87
+ }
88
+ }
89
+ }
90
+ if(refined.size===0)refined.add(`0:${Math.min(text.length,query.length)}`);
91
+
92
+ let best=null;
93
+ for(const key of refined){
94
+ const [startText,lengthText]=key.split(':');
95
+ const start=Number(startText);
96
+ const length=Number(lengthText);
97
+ const value=text.slice(start,start+length);
98
+ const cutoff=best?.distance??Number.POSITIVE_INFINITY;
99
+ const distance=boundedLevenshtein(value,query,cutoff);
100
+ if(best&&distance>best.distance)continue;
101
+ const similarity=1-(distance/Math.max(query.length,value.length));
102
+ const candidate={start,end:start+value.length,value,distance,similarity};
103
+ if(!best||
104
+ candidate.distance<best.distance||
105
+ (candidate.distance===best.distance&&candidate.similarity>best.similarity)||
106
+ (candidate.distance===best.distance&&candidate.similarity===best.similarity&&candidate.start<best.start)){
107
+ best=candidate;
108
+ }
109
+ }
110
+ return best??{start:0,end:Math.min(text.length,query.length),value:text.slice(0,query.length),
111
+ distance:boundedLevenshtein(text.slice(0,query.length),query),similarity:0};
112
+ }
113
+
114
+ export function getSimilarityRatio(a,b){
115
+ if(a.length===0&&b.length===0)return 1;
116
+ return 1-(boundedLevenshtein(a,b)/Math.max(a.length,b.length));
117
+ }
@@ -0,0 +1,8 @@
1
+ import { parentPort, workerData } from 'node:worker_threads';
2
+ import { fuzzyIndexOf } from './fuzzy-search-core.mjs';
3
+
4
+ try {
5
+ parentPort.postMessage({ ok:true, result:fuzzyIndexOf(workerData.text,workerData.query) });
6
+ } catch (error) {
7
+ parentPort.postMessage({ ok:false, error:String(error?.stack??error) });
8
+ }
@@ -0,0 +1,34 @@
1
+ import { Worker } from 'node:worker_threads';
2
+
3
+ export const FUZZY_SEARCH_TIMEOUT_MS=30000;
4
+ const WORKER_URL=new URL('./fuzzy-search-worker.mjs',import.meta.url);
5
+
6
+ export function runFuzzySearchInWorker(text,query,timeoutMs=FUZZY_SEARCH_TIMEOUT_MS){
7
+ if(!Number.isInteger(timeoutMs)||timeoutMs<100||timeoutMs>FUZZY_SEARCH_TIMEOUT_MS)throw new Error('fuzzy timeout_ms must be in [100, 30000]');
8
+ return new Promise((resolve,reject)=>{
9
+ const worker=new Worker(WORKER_URL,{type:'module',workerData:{text,query},execArgv:[]});
10
+ worker.unref?.();
11
+ let settled=false;
12
+ let timer;
13
+ const finish=(fn,value)=>{
14
+ if(settled)return;
15
+ settled=true;
16
+ clearTimeout(timer);
17
+ fn(value);
18
+ };
19
+ timer=setTimeout(()=>{
20
+ worker.terminate().catch(()=>{});
21
+ finish(reject,new Error(`fuzzy search timed out after ${timeoutMs}ms`));
22
+ },timeoutMs);
23
+ timer.unref?.();
24
+ worker.once('message',message=>{
25
+ if(message?.ok)finish(resolve,message.result);
26
+ else finish(reject,new Error(`fuzzy search worker failed: ${String(message?.error??'unknown error')}`));
27
+ worker.terminate().catch(()=>{});
28
+ });
29
+ worker.once('error',error=>finish(reject,error));
30
+ worker.once('exit',code=>{
31
+ if(code!==0)finish(reject,new Error(`fuzzy search worker exited with code ${code}`));
32
+ });
33
+ });
34
+ }
@@ -32,7 +32,7 @@ export function registerMachineFsTools(server, { runtime }) {
32
32
  });
33
33
  server.registerTool('machine_fs_read', {
34
34
  description:'Read one bounded byte range from a root-relative regular file.',
35
- inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().nonnegative().default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false), timeout_ms:z.number().int().min(100).max(180000).default(30000) }),
35
+ inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().min(-4*1024*1024).max(4*1024*1024).default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false), timeout_ms:z.number().int().min(100).max(180000).default(30000) }),
36
36
  outputSchema:machineFsReadOutputSchema
37
37
  }, async ({root_id,path,offset,max_bytes,hash_file,timeout_ms}) => {
38
38
  try { return ok(machineFsReadOutputSchema, await runtime.read({rootId:root_id,relativePath:path,offset,maxBytes:max_bytes,hashFile:hash_file,timeoutMs:timeout_ms})); }
@@ -30,7 +30,7 @@ export const machineFsListOutputSchema = z.strictObject({
30
30
  export const machineFsReadOutputSchema = z.strictObject({
31
31
  status: statusOk, error,
32
32
  root_id: z.string().min(1).max(64).optional(), path: pathText.optional(),
33
- offset: z.number().int().nonnegative().optional(), next_offset: z.number().int().nonnegative().optional(),
33
+ offset: z.number().int().nonnegative().optional(), requested_offset: z.number().int().optional(), next_offset: z.number().int().nonnegative().optional(),
34
34
  file_size: z.number().int().nonnegative().optional(), eof: z.boolean().optional(),
35
35
  data_base64: z.string().max(12 * 1024 * 1024).optional(), data_sha256: hex64.optional(),
36
36
  file_sha256: hex64.nullable().optional(), timeout_ms: z.number().int().min(100).max(180000).optional()
@@ -324,24 +324,25 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
324
324
  return { status: 'ok', root_id: rootId, path: found.relative, entries, next_cursor: nextCursor };
325
325
  }
326
326
  async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false, timeoutMs = 30000 }) {
327
- if (!Number.isInteger(offset) || offset < 0) throw new Error('machine fs read offset must be nonnegative integer');
327
+ if (!Number.isInteger(offset) || offset < -policy.max_read_bytes || offset > policy.max_read_bytes) throw new Error(`machine fs read offset must be in [-${policy.max_read_bytes}, ${policy.max_read_bytes}]`);
328
328
  if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > policy.max_read_bytes) throw new Error(`machine fs max_bytes must be in [1, ${policy.max_read_bytes}]`);
329
329
  if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 180000) throw new Error('machine fs read timeout_ms must be in [100, 180000]');
330
330
  const grant = await assertCodingArmed(policy, rootId);
331
331
  const found = await resolveExisting(policy, grant, rootId, relativePath, 'read', false);
332
332
  const info = await lstat(found.resolved);
333
333
  if (!info.isFile()) throw new Error('machine fs read target must be a regular file');
334
- if (offset > info.size) throw new Error('machine fs read offset exceeds file size');
335
- const length = Math.min(maxBytes, info.size - offset);
334
+ const effectiveOffset = offset < 0 ? Math.max(0, info.size + offset) : offset;
335
+ if (effectiveOffset > info.size) throw new Error('machine fs read offset exceeds file size');
336
+ const length = Math.min(maxBytes, info.size - effectiveOffset);
336
337
  await recheckCodingAuthority(policy, grant, rootId);
337
- const worker = await boundedReadWorker({ file: found.resolved, offset, length, hashFile, timeoutMs, workerPath: readWorkerPath });
338
+ const worker = await boundedReadWorker({ file: found.resolved, offset: effectiveOffset, length, hashFile, timeoutMs, workerPath: readWorkerPath });
338
339
  const buffer = Buffer.from(worker.data_base64, 'base64');
339
340
  if (buffer.length !== worker.bytes_read || buffer.length > length) throw new Error('machine fs read worker byte count mismatch');
340
341
  await recheckCodingAuthority(policy, grant, rootId);
341
342
  return {
342
343
  status: 'ok', root_id: rootId, path: found.relative,
343
- offset, next_offset: offset + buffer.length, file_size: info.size,
344
- eof: offset + buffer.length >= info.size, data_base64: buffer.toString('base64'),
344
+ offset: effectiveOffset, requested_offset: offset, next_offset: effectiveOffset + buffer.length, file_size: info.size,
345
+ eof: effectiveOffset + buffer.length >= info.size, data_base64: buffer.toString('base64'),
345
346
  data_sha256: sha256Bytes(buffer), file_sha256: worker.file_sha256,
346
347
  timeout_ms: timeoutMs
347
348
  };
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.2';
1
+ export const DEADBYTE_VERSION = '0.15.5';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -21,7 +21,7 @@ test('npx CLI reports the canonical package version through --version, -v and ve
21
21
  for(const arg of ['--version','-v','version']){
22
22
  const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
23
23
  assert.equal(result.status,0,result.stderr);
24
- assert.equal(result.stdout.trim(),'0.15.2');
24
+ assert.equal(result.stdout.trim(),'0.15.5');
25
25
  }
26
26
  });
27
27
 
@@ -50,6 +50,6 @@ test('npx CLI exposes local supervisor lifecycle without any supervisor arm comm
50
50
 
51
51
  test('README makes npx the primary operator UX',async()=>{
52
52
  const readme=await readFile(path.join(root,'README.txt'),'utf8');
53
- assert.match(readme,/npx -y deadbyte-mcp@0\.15\.2 remote/);
54
- assert.match(readme,/npx -y deadbyte-mcp@0\.15\.2 audit verify/);
53
+ assert.match(readme,/npx -y deadbyte-mcp@0\.15\.5 remote/);
54
+ assert.match(readme,/npx -y deadbyte-mcp@0\.15\.5 audit verify/);
55
55
  });
@@ -105,7 +105,7 @@ test('proof execution creates deterministic output and signed verifiable receipt
105
105
  assert.equal(result.receipt.request.schema, 'deadbyte.request.v1');
106
106
  assert.match(result.receipt.request.nonce, /^[0-9a-f]{64}$/);
107
107
  assert.match(result.receipt.request.sha256, /^[0-9a-f]{64}$/);
108
- assert.equal(result.receipt.runtime.package_version, '0.15.2');
108
+ assert.equal(result.receipt.runtime.package_version, '0.15.5');
109
109
  assert.match(result.receipt.runtime.mcp_ingress_adapter, /^(?:none|tunnel-stdio-probe-compat-v1)$/);
110
110
  assert.match(result.receipt.runtime.mcp_ingress_adapter_sha256, /^[0-9a-f]{64}$/);
111
111
  assert.match(result.receipt.runtime.mcp_ingress_mode_source_sha256, /^[0-9a-f]{64}$/);
@@ -15,7 +15,9 @@ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy
15
15
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
16
  const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
17
 
18
- test('R44 real max-runtime full stdio catalog is exactly 81 with skill and four Desktop primitives while authority remains disarmed',async()=>{
18
+ test('R44 real max-runtime full stdio catalog is exactly 81 with skill and four Desktop primitives while authority remains disarmed',async t=>{
19
+ const current=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8'));
20
+ if(current.version!=='0.15.0'){t.skip('historical R44 catalog; current development release is '+current.version);return;}
19
21
  const f=await createR34Fixture({authority:'disarmed'});
20
22
  const processPolicyPath=path.join(f.root,'process-policy.json');
21
23
  const processGrant=path.join(f.root,'process-grant.json');
@@ -15,7 +15,9 @@ const desktopPolicyPath=path.join(repoRoot,'controller','deadbyte-desktop-policy
15
15
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
16
16
  const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
17
17
 
18
- test('R45 real max-runtime full stdio catalog is exactly 82 with skill and four Desktop primitives while authority remains disarmed',async()=>{
18
+ test('R45 real max-runtime full stdio catalog is exactly 82 with skill and four Desktop primitives while authority remains disarmed',async t=>{
19
+ const current=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8'));
20
+ if(current.version!=='0.15.1'){t.skip('historical R45 catalog; current development release is '+current.version);return;}
19
21
  const f=await createR34Fixture({authority:'disarmed'});
20
22
  const processPolicyPath=path.join(f.root,'process-policy.json');
21
23
  const processGrant=path.join(f.root,'process-grant.json');