deadbyte-mcp 0.15.18 → 0.15.19

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.
Files changed (55) hide show
  1. package/MANIFEST.SHA256 +54 -32
  2. package/PROVENANCE.json +13 -7
  3. package/README.txt +7 -7
  4. package/RELEASE.json +25 -6
  5. package/SBOM.cdx.json +3 -3
  6. package/TEST-MATRIX.json +26 -7
  7. package/controller/deadbyte-controller.ps1 +56 -1
  8. package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +247 -44
  9. package/docs/COMMANDER_REFERENCE_0_2_51.json +46 -0
  10. package/docs/R47-PROVENANCE.json +11 -0
  11. package/docs/R47-RELEASE.json +58 -0
  12. package/docs/plans/2026-09-27-desktop-commander-drop-in-parity.md +92 -0
  13. package/package.json +4 -2
  14. package/proof/commander-closure-20260927/CONTINUATION.json +43 -0
  15. package/proof/commander-closure-20260927/RESULT.json +30 -0
  16. package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +30 -0
  17. package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +13 -0
  18. package/scripts/deploy-r48.mjs +86 -0
  19. package/scripts/full-regression-r48.mjs +28 -0
  20. package/src/coding-search.mjs +65 -7
  21. package/src/commander-compat-contracts.mjs +10 -0
  22. package/src/commander-core-bridge.mjs +2 -2
  23. package/src/commander-extended-bridge-r47.mjs +116 -12
  24. package/src/commander-extended-runtime.mjs +33 -6
  25. package/src/commander-external-process-r48.mjs +125 -0
  26. package/src/commander-observability.mjs +66 -16
  27. package/src/commander-office-reader-r48.mjs +548 -0
  28. package/src/commander-pdf-r48.mjs +448 -0
  29. package/src/commander-ui-resources.mjs +26 -0
  30. package/src/commander-url-reader-r48.mjs +201 -0
  31. package/src/commander-wire-adapter-r47.mjs +595 -101
  32. package/src/deadbyte-cli.mjs +73 -8
  33. package/src/mcp-server.mjs +9 -2
  34. package/src/observation-output-contracts.mjs +2 -1
  35. package/src/observation-runtime.mjs +4 -2
  36. package/src/version.mjs +1 -1
  37. package/test/coding-search-r36.test.mjs +71 -0
  38. package/test/commander-core-bridge-r47.test.mjs +32 -3
  39. package/test/commander-exhaustive-surface-r47.test.mjs +5 -1
  40. package/test/commander-extended-runtime-r47.test.mjs +8 -0
  41. package/test/commander-extended-wire-r48.test.mjs +98 -0
  42. package/test/commander-external-process-r48.test.mjs +76 -0
  43. package/test/commander-office-reader-r48.test.mjs +152 -0
  44. package/test/commander-pdf-r48.test.mjs +103 -0
  45. package/test/commander-protocol-r47.test.mjs +23 -1
  46. package/test/commander-reference-contract-r48.test.mjs +26 -0
  47. package/test/commander-schema-differential-r48.test.mjs +88 -0
  48. package/test/commander-url-reader-r48.test.mjs +68 -0
  49. package/test/commander-wire-contract-r47.test.mjs +461 -41
  50. package/test/controller-r212.test.mjs +23 -2
  51. package/test/observation-runtime.test.mjs +4 -3
  52. package/test/prompts-resources.test.mjs +1 -1
  53. package/test/r47-release-contract.test.mjs +9 -8
  54. package/test/r48-release-contract.test.mjs +36 -0
  55. package/test/remote-session-cli.test.mjs +34 -1
@@ -0,0 +1,152 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readDocxBytes, editDocxBytes, readXlsxBytes, editXlsxBytes, createXlsxBytes, appendXlsxBytes } from '../src/commander-office-reader-r48.mjs';
4
+
5
+ function crc32(buf){
6
+ let c=0xffffffff;
7
+ for(const b of buf){c^=b;for(let k=0;k<8;k++)c=(c>>>1)^((c&1)?0xedb88320:0);}
8
+ return (c^0xffffffff)>>>0;
9
+ }
10
+ function zipStored(entries){
11
+ const locals=[],centrals=[]; let offset=0;
12
+ for(const [name,value] of Object.entries(entries)){
13
+ const n=Buffer.from(name,'utf8'),data=Buffer.from(value,'utf8'),crc=crc32(data);
14
+ const local=Buffer.alloc(30);
15
+ local.writeUInt32LE(0x04034b50,0);local.writeUInt16LE(20,4);local.writeUInt16LE(0x800,6);local.writeUInt16LE(0,8);
16
+ local.writeUInt32LE(crc,14);local.writeUInt32LE(data.length,18);local.writeUInt32LE(data.length,22);local.writeUInt16LE(n.length,26);
17
+ locals.push(local,n,data);
18
+ const central=Buffer.alloc(46);
19
+ central.writeUInt32LE(0x02014b50,0);central.writeUInt16LE(20,4);central.writeUInt16LE(20,6);central.writeUInt16LE(0x800,8);central.writeUInt16LE(0,10);
20
+ central.writeUInt32LE(crc,16);central.writeUInt32LE(data.length,20);central.writeUInt32LE(data.length,24);central.writeUInt16LE(n.length,28);central.writeUInt32LE(offset,42);
21
+ centrals.push(central,n);
22
+ offset+=local.length+n.length+data.length;
23
+ }
24
+ const centralBytes=Buffer.concat(centrals),localBytes=Buffer.concat(locals);
25
+ const eocd=Buffer.alloc(22);
26
+ eocd.writeUInt32LE(0x06054b50,0);eocd.writeUInt16LE(Object.keys(entries).length,8);eocd.writeUInt16LE(Object.keys(entries).length,10);
27
+ eocd.writeUInt32LE(centralBytes.length,12);eocd.writeUInt32LE(localBytes.length,16);
28
+ return Buffer.concat([localBytes,centralBytes,eocd]);
29
+ }
30
+
31
+ const DOCX=zipStored({
32
+ 'word/document.xml':'<?xml version="1.0"?><w:document xmlns:w="x"><w:body><w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>Hello &amp; world</w:t></w:r></w:p><w:p><w:r><w:t>Second</w:t></w:r><w:tab/><w:r><w:t>line</w:t></w:r></w:p><w:tbl><w:tr><w:tc><w:p><w:r><w:t>A</w:t></w:r></w:p></w:tc><w:tc><w:p><w:r><w:t>B</w:t></w:r></w:p></w:tc></w:tr></w:tbl></w:body></w:document>'
33
+ });
34
+
35
+ const XLSX=zipStored({
36
+ 'xl/workbook.xml':'<workbook xmlns:r="r"><sheets><sheet name="Data" sheetId="1" r:id="rId1"/></sheets></workbook>',
37
+ 'xl/_rels/workbook.xml.rels':'<Relationships><Relationship Id="rId1" Target="worksheets/sheet1.xml"/></Relationships>',
38
+ 'xl/sharedStrings.xml':'<sst><si><t>Name</t></si><si><t>Alpha</t></si><si><t>Beta</t></si></sst>',
39
+ 'xl/worksheets/sheet1.xml':'<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c><c r="B1" t="inlineStr"><is><t>Count</t></is></c></row><row r="2"><c r="A2" t="s"><v>1</v></c><c r="B2"><v>42</v></c></row><row r="3"><c r="A3" t="s"><v>2</v></c><c r="B3" t="b"><v>1</v></c></row></sheetData></worksheet>'
40
+ });
41
+
42
+ test('R48 native DOCX reader produces text-bearing outline and paged raw XML',()=>{
43
+ const outline=readDocxBytes(DOCX,{path:'demo.docx',offset:0,length:1000});
44
+ assert.equal(outline.mimeType,'text/plain');
45
+ assert.match(outline.content,/\[0\] w:p style="Heading1"/);
46
+ assert.match(outline.content,/Hello & world/);
47
+ assert.match(outline.content,/Second\tline/);
48
+ assert.match(outline.content,/w:tbl/);
49
+ assert.match(outline.content,/A \| B/);
50
+ const raw=readDocxBytes(DOCX,{path:'demo.docx',offset:1,length:3});
51
+ assert.equal(raw.mimeType,'application/xml');
52
+ assert.match(raw.content,/\[DOCX XML: lines 1-/);
53
+ assert.equal(raw.metadata.isDocx,true);
54
+ });
55
+
56
+ test('R48 native XLSX reader preserves types, sheet selection, A1 range, and row pagination',()=>{
57
+ const range=readXlsxBytes(XLSX,{path:'book.xlsx',sheet:'Data',range:'A1:B2',offset:0,length:100});
58
+ assert.deepEqual(range.data,[['Name','Count'],['Alpha',42]]);
59
+ assert.equal(range.sheetName,'Data');
60
+ assert.equal(range.mimeType,'application/json');
61
+ assert.deepEqual(range.metadata.sheets.map(s=>s.name),['Data']);
62
+ const page=readXlsxBytes(XLSX,{path:'book.xlsx',sheet:'Data',offset:1,length:1});
63
+ assert.deepEqual(page.data,[['Alpha',42]]);
64
+ const embedded=readXlsxBytes(XLSX,{path:'book.xlsx',range:"'Data'!A2:B3",offset:0,length:100});
65
+ assert.deepEqual(embedded.data,[['Alpha',42],['Beta',true]]);
66
+ });
67
+
68
+ test('R48 office ZIP parser rejects traversal and CRC corruption',()=>{
69
+ const evil=zipStored({'../word/document.xml':'x'});
70
+ assert.throws(()=>readDocxBytes(evil,{path:'evil.docx'}),/unsafe ZIP entry/);
71
+ const corrupt=Buffer.from(DOCX);
72
+ const marker=Buffer.from('Hello &amp; world');
73
+ const at=corrupt.indexOf(marker);
74
+ assert.ok(at>0); corrupt[at]^=1;
75
+ assert.throws(()=>readDocxBytes(corrupt,{path:'corrupt.docx'}),/CRC32 mismatch/);
76
+ });
77
+
78
+ test('R48 native XLSX range editor rewrites existing cells and creates missing rows/cells without shared-string mutation',()=>{
79
+ const edited=editXlsxBytes(XLSX,{
80
+ range:"'Data'!A2:C4",
81
+ content:[
82
+ ['Gamma',7,true],
83
+ ['Delta',8,false],
84
+ ['New',9,null]
85
+ ]
86
+ });
87
+ const read=readXlsxBytes(edited.bytes,{path:'book.xlsx',sheet:'Data',range:'A1:C4',offset:0,length:100});
88
+ assert.deepEqual(read.data,[
89
+ ['Name','Count',null],
90
+ ['Gamma',7,true],
91
+ ['Delta',8,false],
92
+ ['New',9,null]
93
+ ]);
94
+ assert.match(edited.before_sha256,/^[0-9a-f]{64}$/);
95
+ assert.match(edited.after_sha256,/^[0-9a-f]{64}$/);
96
+ assert.notEqual(edited.before_sha256,edited.after_sha256);
97
+ assert.equal(edited.updated_cells,9);
98
+ });
99
+
100
+ test('R48 native XLSX range editor fails closed on shape mismatch and unsupported value type',()=>{
101
+ assert.throws(()=>editXlsxBytes(XLSX,{range:'Data!A1:B2',content:[[1,2],[3]]}),/shape mismatch/);
102
+ assert.throws(()=>editXlsxBytes(XLSX,{range:'Data!A1',content:[[{}]]}),/unsupported XLSX cell value/);
103
+ });
104
+
105
+ test('R48 native XLSX creator emits deterministic multi-sheet OOXML readable by the same parser',()=>{
106
+ const made=createXlsxBytes({
107
+ Sheet1:[['Name','Count'],['Alpha',1]],
108
+ Data:[['Flag','Value'],[true,2]]
109
+ });
110
+ const s1=readXlsxBytes(made.bytes,{path:'new.xlsx',sheet:'Sheet1',range:'A1:B2'});
111
+ const data=readXlsxBytes(made.bytes,{path:'new.xlsx',sheet:'Data',range:'A1:B2'});
112
+ assert.deepEqual(s1.data,[['Name','Count'],['Alpha',1]]);
113
+ assert.deepEqual(data.data,[['Flag','Value'],[true,2]]);
114
+ assert.match(made.bytes_sha256,/^[0-9a-f]{64}$/);
115
+ assert.equal(made.sheet_count,2);
116
+ const again=createXlsxBytes({Sheet1:[['Name','Count'],['Alpha',1]],Data:[['Flag','Value'],[true,2]]});
117
+ assert.equal(again.bytes_sha256,made.bytes_sha256);
118
+ });
119
+
120
+ test('R48 native XLSX append preserves existing package and appends rows to an existing sheet',()=>{
121
+ const base=createXlsxBytes({Sheet1:[['A',1],['B',2]]});
122
+ const appended=appendXlsxBytes(base.bytes,{Sheet1:[['C',3],['D',4]]});
123
+ const read=readXlsxBytes(appended.bytes,{path:'append.xlsx',sheet:'Sheet1',range:'A1:B4'});
124
+ assert.deepEqual(read.data,[['A',1],['B',2],['C',3],['D',4]]);
125
+ assert.equal(appended.appended_rows,2);
126
+ assert.throws(()=>appendXlsxBytes(base.bytes,{Missing:[[1]]}),/Sheet "Missing" not found/);
127
+ });
128
+
129
+ test('R48 native XLSX creator rejects unsafe sheet names and non-rectangular data',()=>{
130
+ assert.throws(()=>createXlsxBytes({'bad/name':[[1]]}),/sheet name invalid/);
131
+ assert.throws(()=>createXlsxBytes({Sheet1:[[1,2],[3]]}),/rectangular/);
132
+ });
133
+
134
+ test('R48 native DOCX editor performs exact pretty-XML replacement and repacks a valid document',()=>{
135
+ const raw=readDocxBytes(DOCX,{path:'demo.docx',offset:1,length:200});
136
+ assert.match(raw.content,/Hello &amp; world/);
137
+ const edited=editDocxBytes(DOCX,{
138
+ oldString:'<w:t>Hello &amp; world</w:t>',
139
+ newString:'<w:t>Hello DEADBYTE</w:t>',
140
+ expectedReplacements:1
141
+ });
142
+ const read=readDocxBytes(edited.bytes,{path:'demo.docx',offset:0,length:1000});
143
+ assert.match(read.content,/Hello DEADBYTE/);
144
+ assert.doesNotMatch(read.content,/Hello & world/);
145
+ assert.equal(edited.replacements,1);
146
+ assert.match(edited.before_sha256,/^[0-9a-f]{64}$/);
147
+ assert.match(edited.after_sha256,/^[0-9a-f]{64}$/);
148
+ });
149
+
150
+ test('R48 native DOCX editor refuses ambiguous replacement counts before repack',()=>{
151
+ assert.throws(()=>editDocxBytes(DOCX,{oldString:'<w:p>',newString:'<w:p>',expectedReplacements:1}),/expected 1 replacements/);
152
+ });
@@ -0,0 +1,103 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import {pathToFileURL} from 'node:url';
6
+ import { createDeadbytePdf, parseDeadbytePdf, editDeadbytePdf, readPdfText } from '../src/commander-pdf-r48.mjs';
7
+
8
+ test('R48 ordinary PDF with compressed object streams preserves pages through independent oracle reload',async()=>{
9
+ const oracle=process.env.DEADBYTE_PDF_TEST_ORACLE??path.join(os.homedir(),'.deadbyte-mcp','evidence','commander-oracle','node_modules','pdf-lib','cjs','index.js');
10
+ const {PDFDocument}=await import(pathToFileURL(oracle).href);
11
+ const reference=await PDFDocument.create();
12
+ reference.addPage([321,456]);reference.addPage([654,789]);
13
+ const fixture=await reference.save({useObjectStreams:true});
14
+ const edited=await editDeadbytePdf(fixture,[{type:'delete',pageIndexes:[0]}]);
15
+ const reloaded=await PDFDocument.load(edited.bytes);
16
+ assert.equal(reloaded.getPageCount(),1);
17
+ assert.equal(reloaded.getPage(0).getWidth(),654);
18
+ assert.equal(reloaded.getPage(0).getHeight(),789);
19
+ });
20
+
21
+ test('R48 compressed object streams reject duplicate identity and malformed indexes before mutation',async()=>{
22
+ const {deflateSync}=await import('node:zlib');
23
+ const make=(index,body,n,first)=>{
24
+ const stream=deflateSync(Buffer.from(index+body,'latin1'));
25
+ return Buffer.concat([Buffer.from(`%PDF-1.5\n1 0 obj << /Type /ObjStm /N ${n} /First ${first} /Filter /FlateDecode /Length ${stream.length} >>\nstream\n`),stream,Buffer.from('\nendstream\nendobj\n%%EOF')]);
26
+ };
27
+ assert.throws(()=>readPdfText(make('1 0 ','<< /Type /Catalog >>',1,4)),/duplicate object/);
28
+ assert.throws(()=>readPdfText(make('2 999 ','<< /Type /Catalog >>',1,6)),/entry invalid/);
29
+ assert.throws(()=>readPdfText(make('2 0 ','<< /Type /Catalog >>',2,4)),/count mismatch/);
30
+ });
31
+
32
+ test('R48 native PDF profile round-trips exact page text and exposes stable page count',()=>{
33
+ const made=createDeadbytePdf(['alpha','beta\ngamma']);
34
+ assert.equal(made.mime_type,'application/pdf');
35
+ assert.match(made.bytes.subarray(0,64).toString('ascii'),/%PDF-1\.4/);
36
+ const parsed=parseDeadbytePdf(made.bytes);
37
+ assert.deepEqual(parsed.pages,['alpha','beta\ngamma']);
38
+ assert.equal(parsed.page_count,2);
39
+ assert.equal(parsed.profile,'deadbyte-pdf-r48');
40
+ });
41
+
42
+ test('R48 PDF delete uses Desktop Commander negative-index semantics without page confusion',async()=>{
43
+ const base=createDeadbytePdf(['p0','p1','p2']).bytes;
44
+ const edited=await editDeadbytePdf(base,[{type:'delete',pageIndexes:[-1,99,-3,-3]}]);
45
+ assert.deepEqual(parseDeadbytePdf(edited.bytes).pages,['p1']);
46
+ });
47
+
48
+ test('R48 PDF insert supports markdown and source PDF pages with exact ordered insertion',async()=>{
49
+ const base=createDeadbytePdf(['A','D']).bytes;
50
+ const source=createDeadbytePdf(['B','C']).bytes;
51
+ const edited=await editDeadbytePdf(base,[
52
+ {type:'insert',pageIndex:1,markdown:'X'},
53
+ {type:'insert',pageIndex:2,sourcePdfPath:'source.pdf'}
54
+ ],{
55
+ sourceResolver:async path=>{
56
+ assert.equal(path,'source.pdf');
57
+ return source;
58
+ }
59
+ });
60
+ assert.deepEqual(parseDeadbytePdf(edited.bytes).pages,['A','X','B','C','D']);
61
+ });
62
+
63
+ test('R48 ordinary classic PDF surgery preserves selected content graphs and supports inserts',async()=>{
64
+ const ordinary=Buffer.from(createDeadbytePdf(['A','B']).bytes.toString('latin1').replace('%DEADBYTE-PDF-R48','%ORDINARY-PDF-R48 '),'latin1');
65
+ const source=Buffer.from(createDeadbytePdf(['C']).bytes.toString('latin1').replace('%DEADBYTE-PDF-R48','%ORDINARY-PDF-R48 '),'latin1');
66
+ const edited=await editDeadbytePdf(ordinary,[
67
+ {type:'delete',pageIndexes:[0]},
68
+ {type:'insert',pageIndex:1,markdown:'X'},
69
+ {type:'insert',pageIndex:2,sourcePdfPath:'source.pdf'}
70
+ ],{sourceResolver:async()=>source});
71
+ const read=readPdfText(edited.bytes);
72
+ assert.equal(read.profile,'generic-pdf-r48');
73
+ assert.equal(read.page_count,3);
74
+ assert.deepEqual(read.pages,['B','X','C']);
75
+ });
76
+
77
+ test('R48 generic PDF reader extracts bounded text from classic plain content streams',()=>{
78
+ const pdf=Buffer.from('%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /Contents 4 0 R >>\nendobj\n4 0 obj\n<< /Length 74 >>\nstream\nBT /F1 12 Tf 72 720 Td (Hello\\040foreign) Tj 0 -14 Td (page one) Tj ET\nendstream\nendobj\n%%EOF\n','latin1');
79
+ const parsed=readPdfText(pdf);
80
+ assert.equal(parsed.profile,'generic-pdf-r48');
81
+ assert.equal(parsed.page_count,1);
82
+ assert.match(parsed.pages[0],/Hello foreign/);
83
+ assert.match(parsed.pages[0],/page one/);
84
+ assert.equal(parsed.partial,false);
85
+ });
86
+
87
+ test('R48 generic PDF reader inflates Flate streams and extracts TJ plus hex strings',async()=>{
88
+ const {deflateSync}=await import('node:zlib');
89
+ const raw='BT /F1 12 Tf 72 720 Td [(Second) 120 ( page)] TJ T* <7468726565> Tj ET';
90
+ const compressed=deflateSync(Buffer.from(raw,'latin1'));
91
+ const head=Buffer.from(`%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /Contents 4 0 R >>\nendobj\n4 0 obj\n<< /Filter /FlateDecode /Length ${compressed.length} >>\nstream\n`,'latin1');
92
+ const tail=Buffer.from('\nendstream\nendobj\n%%EOF\n','latin1');
93
+ const parsed=readPdfText(Buffer.concat([head,compressed,tail]));
94
+ assert.equal(parsed.page_count,1);
95
+ assert.match(parsed.pages[0],/Second page/);
96
+ assert.match(parsed.pages[0],/three/);
97
+ assert.equal(parsed.partial,false);
98
+ });
99
+
100
+ test('R48 generic PDF reader fails closed when page content uses an unsupported filter',()=>{
101
+ const pdf=Buffer.from('%PDF-1.4\n1 0 obj\n<< /Type /Page /Contents 2 0 R >>\nendobj\n2 0 obj\n<< /Filter /LZWDecode >>\nstream\nabc\nendstream\nendobj\n%%EOF\n','latin1');
102
+ assert.throws(()=>readPdfText(pdf),/unsupported PDF stream filter/);
103
+ });
@@ -18,7 +18,10 @@ const execFileAsync=promisify(execFile);
18
18
  async function materializeTestHostPolicy(root){
19
19
  const policyPath=path.join(root,'deadbyte-host-policy.json');
20
20
  const script=path.join(repoRoot,'scripts','init-host-policy.ps1');
21
- await execFileAsync('powershell.exe',['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',script,'-CoreRoot',repoRoot,'-BridgeRoot',repoRoot,'-PolicyPath',policyPath],{windowsHide:true});
21
+ // Windows PowerShell must derive its own module path, not inherit incompatible
22
+ // PowerShell 7 modules from the verifier's parent process.
23
+ const env={...process.env};delete env.PSModulePath;
24
+ await execFileAsync('powershell.exe',['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',script,'-CoreRoot',repoRoot,'-BridgeRoot',repoRoot,'-PolicyPath',policyPath],{windowsHide:true,env});
22
25
  return policyPath;
23
26
  }
24
27
 
@@ -41,5 +44,24 @@ test('Commander R47 exact 40-name surface is registered over real MCP stdio',asy
41
44
  for(const name of expected) assert.ok(names.includes(name),'missing tool: '+name);
42
45
  assert.equal(new Set(expected).size,40);
43
46
  for(const name of expected){const t=listed.tools.find(x=>x.name===name);assert.ok(t.inputSchema,'missing schema: '+name);}
47
+ assert.equal(names.includes('track_ui_event'),false,'hidden UI dispatch must not be advertised');
48
+ const hidden=await client.callTool({name:'track_ui_event',arguments:{event:'preview_opened',component:'file_preview',params:{kind:'docx'}}});
49
+ assert.equal(hidden.isError,undefined,JSON.stringify(hidden));
50
+ assert.equal(hidden.structuredContent?.accepted,true);
51
+ const resources=await client.listResources();
52
+ assert.ok(resources.resources.some(resource=>resource.uri==='deadbyte://context'));
53
+ for(const uri of ['ui://desktop-commander/file-preview','ui://desktop-commander/config-editor']){
54
+ assert.ok(resources.resources.some(resource=>resource.uri===uri),`missing reference UI resource ${uri}`);
55
+ const response=await client.readResource({uri});
56
+ assert.equal(response.contents[0].mimeType,'text/html;profile=mcp-app');
57
+ assert.ok(response.contents[0].text.length<65536);
58
+ assert.match(response.contents[0].text,/Content-Security-Policy/);
59
+ assert.doesNotMatch(response.contents[0].text,/<script[^>]+src=|https?:\/\//);
60
+ }
61
+ const context=await client.readResource({uri:'deadbyte://context'});
62
+ assert.equal(context.contents[0].mimeType,'text/markdown');
63
+ assert.ok(context.contents[0].text.length>0&&context.contents[0].text.length<1024*1024);
64
+ const templates=await client.listResourceTemplates();
65
+ assert.deepEqual(templates.resourceTemplates,[]);
44
66
  }finally{await client.close().catch(()=>{});await rm(root,{recursive:true,force:true}).catch(()=>{});}
45
67
  });
@@ -0,0 +1,26 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+ import * as compat from '../src/commander-compat-contracts.mjs';
5
+
6
+ const expectedPublic=Object.freeze([
7
+ 'get_config','set_config_value','read_file','read_multiple_files','write_file','write_pdf',
8
+ 'create_directory','list_directory','move_file','start_search','get_more_search_results',
9
+ 'stop_search','list_searches','get_file_info','edit_block','start_process',
10
+ 'read_process_output','interact_with_process','force_terminate','list_sessions',
11
+ 'list_processes','kill_process','get_usage_stats','get_recent_tool_calls',
12
+ 'give_feedback_to_desktop_commander','get_prompts'
13
+ ].sort());
14
+
15
+ test('R48 pins Desktop Commander 0.2.51 advertised public wire separately from DEADBYTE additive tools',async()=>{
16
+ assert.ok(Array.isArray(compat.COMMANDER_REFERENCE_PUBLIC_TOOLS),'reference public tool contract missing');
17
+ assert.deepEqual([...compat.COMMANDER_REFERENCE_PUBLIC_TOOLS].sort(),expectedPublic);
18
+ assert.ok(compat.COMMANDER_COMPAT_TOOLS.length>compat.COMMANDER_REFERENCE_PUBLIC_TOOLS.length);
19
+ const doc=JSON.parse(await readFile(new URL('../docs/COMMANDER_EXHAUSTIVE_SURFACE.json',import.meta.url),'utf8'));
20
+ assert.equal(doc.reference.server_sha256,'dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305');
21
+ assert.equal(doc.reference.schemas_sha256,'f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140');
22
+ assert.deepEqual([...doc.reference_public_surface].sort(),expectedPublic);
23
+ assert.equal(doc.reference_hidden_dispatch.includes('track_ui_event'),true);
24
+ assert.equal(doc.status_counts.reference_public,26);
25
+ assert.equal(doc.status_counts.deadbyte_additive,doc.surface.length-26);
26
+ });
@@ -0,0 +1,88 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {readFile} from 'node:fs/promises';
5
+ import {createCommanderWireAdapter} from '../src/commander-wire-adapter-r47.mjs';
6
+ import {registerCommanderExtendedBridge} from '../src/commander-extended-bridge-r47.mjs';
7
+
8
+ const referencePath='D:\\Projects\\deadbyte-mcp\\desktop-commander\\dist\\tools\\schemas.js';
9
+ const expectedHash='f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140';
10
+ async function referenceSchemas(){
11
+ let source=await readFile(referencePath,'utf8');
12
+ assert.equal(createHash('sha256').update(source).digest('hex'),expectedHash);
13
+ const zodUrl=new URL('../node_modules/zod/v3/index.js',import.meta.url).href;
14
+ source=source.replace('from "zod"',`from ${JSON.stringify(zodUrl)}`);
15
+ return (await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`)).toolArgSchemas;
16
+ }
17
+ function candidateSchemas(){
18
+ const schemas={...createCommanderWireAdapter({}).schemas};
19
+ registerCommanderExtendedBridge({registerTool(name,definition){schemas[name]=definition.inputSchema;}},{extendedRuntime:{}});
20
+ return schemas;
21
+ }
22
+ const valid={
23
+ get_config:{},set_config_value:{key:'k',value:'v'},read_file:{path:'x'},read_multiple_files:{paths:['x']},
24
+ write_file:{path:'x',content:'v'},write_pdf:{path:'x',content:'# x'},create_directory:{path:'x'},
25
+ list_directory:{path:'x'},move_file:{source:'a',destination:'b'},start_search:{path:'.',pattern:'x'},
26
+ get_more_search_results:{sessionId:'s'},stop_search:{sessionId:'s'},list_searches:{},get_file_info:{path:'x'},
27
+ edit_block:{file_path:'x',old_string:'a',new_string:'b'},start_process:{command:'echo x',timeout_ms:10},
28
+ read_process_output:{pid:1},interact_with_process:{pid:1,input:'x'},force_terminate:{pid:1},list_sessions:{},
29
+ list_processes:{},kill_process:{pid:1},get_usage_stats:{},get_recent_tool_calls:{},
30
+ give_feedback_to_desktop_commander:{},get_prompts:{action:'get_prompt',promptId:'x'}
31
+ };
32
+
33
+ function objectShape(schema){
34
+ while(schema._def?.schema)schema=schema._def.schema;
35
+ return schema.shape;
36
+ }
37
+
38
+ test('R48 exhaustive public property and required/default semantics match the pinned reference',async()=>{
39
+ const reference=await referenceSchemas(),candidate=candidateSchemas();
40
+ const values=[undefined,null,false,true,-1,0,1,1.5,1000,1001,'','x','ui','llm','files','content','rewrite','append','get_prompt','2026-09-27T00:00:00Z','bad',[],['x'],[1],{}, {x:1}];
41
+ for(const [name,base] of Object.entries(valid)){
42
+ const expectedKeys=Object.keys(objectShape(reference[name])).sort();
43
+ const actualKeys=Object.keys(objectShape(candidate[name])).sort();
44
+ assert.deepEqual(actualKeys,expectedKeys,`${name} public properties`);
45
+ const compare=input=>{
46
+ const expected=reference[name].safeParse(input),actual=candidate[name].safeParse(input);
47
+ assert.equal(actual.success,expected.success,`${name} ${JSON.stringify(input)} acceptance`);
48
+ if(actual.success)assert.deepEqual(actual.data,expected.data,`${name} ${JSON.stringify(input)} defaults/normalization`);
49
+ };
50
+ for(const field of expectedKeys){
51
+ const absent={...base};delete absent[field];compare(absent);
52
+ for(const value of values)compare({...base,[field]:value});
53
+ }
54
+ }
55
+ });
56
+
57
+ test('R48 all 26 advertised schemas accept, normalize defaults, and strip unknown keys like pinned reference',async()=>{
58
+ const reference=await referenceSchemas(),candidate=candidateSchemas();
59
+ assert.deepEqual(Object.keys(valid).sort(),Object.keys(reference).filter(name=>name!=='track_ui_event').sort());
60
+ for(const [name,input] of Object.entries(valid)){
61
+ assert.ok(candidate[name],`candidate schema missing ${name}`);
62
+ const withUnknown={...input,__reference_unknown:'strip-me'};
63
+ const expected=reference[name].safeParse(withUnknown),actual=candidate[name].safeParse(withUnknown);
64
+ assert.equal(actual.success,expected.success,`${name} acceptance mismatch`);
65
+ assert.deepEqual(actual.success?actual.data:null,expected.success?expected.data:null,`${name} normalized value mismatch`);
66
+ }
67
+ });
68
+
69
+ test('R48 material enum union refinement and constraint probes match pinned reference schemas',async()=>{
70
+ const reference=await referenceSchemas(),candidate=candidateSchemas();
71
+ const probes={
72
+ get_config:[{origin:'bad'}],set_config_value:[{key:'k',value:{bad:true}}],read_file:[{path:'x',isUrl:'yes'}],
73
+ read_multiple_files:[{paths:[1]}],write_file:[{path:'x',content:'x',mode:'bad'}],
74
+ write_pdf:[{path:'x',content:'[{"type":"delete","pageIndexes":[0]}]'},{path:'x',content:'[{"type":"bad"}]'}],
75
+ create_directory:[{}],list_directory:[{path:'x',origin:'bad'}],move_file:[{source:'a'}],
76
+ start_search:[{path:'.',pattern:'x',searchType:'bad'}],get_more_search_results:[{sessionId:1}],
77
+ stop_search:[{}],list_searches:[1],get_file_info:[{}],edit_block:[{file_path:'x'}],
78
+ start_process:[{command:'x'}],read_process_output:[{pid:'1'}],interact_with_process:[{pid:1}],
79
+ force_terminate:[{}],list_sessions:[1],list_processes:[1],kill_process:[{pid:'1'}],get_usage_stats:[1],
80
+ get_recent_tool_calls:[{maxResults:0},{maxResults:1.5},{since:'bad'}],give_feedback_to_desktop_commander:[1],
81
+ get_prompts:[{action:'bad',promptId:'x'}]
82
+ };
83
+ for(const [name,inputs] of Object.entries(probes)) for(const input of inputs){
84
+ const expected=reference[name].safeParse(input),actual=candidate[name].safeParse(input);
85
+ assert.equal(actual.success,expected.success,`${name} mismatch for ${JSON.stringify(input)}`);
86
+ if(actual.success)assert.deepEqual(actual.data,expected.data,`${name} normalized mismatch`);
87
+ }
88
+ });
@@ -0,0 +1,68 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createCommanderUrlReader, isPublicInternetAddress } from '../src/commander-url-reader-r48.mjs';
4
+
5
+ test('R48 URL reader classifies public vs local/reserved addresses conservatively',()=>{
6
+ for(const ip of ['127.0.0.1','10.0.0.1','172.16.0.1','192.168.1.1','169.254.1.1','100.64.0.1','0.0.0.0','224.0.0.1','::1','fe80::1','fc00::1','2001:db8::1']) {
7
+ assert.equal(isPublicInternetAddress(ip),false,ip);
8
+ }
9
+ for(const ip of ['8.8.8.8','1.1.1.1','2606:4700:4700::1111']) {
10
+ assert.equal(isPublicInternetAddress(ip),true,ip);
11
+ }
12
+ });
13
+
14
+ test('R48 URL reader rejects private DNS answers before network transport',async()=>{
15
+ let requests=0;
16
+ const reader=createCommanderUrlReader({
17
+ lookupFn:async()=>[{address:'127.0.0.1',family:4}],
18
+ requestOnceFn:async()=>{requests++;return {status:200,headers:{'content-type':'text/plain'},body:Buffer.from('no')};}
19
+ });
20
+ await assert.rejects(reader('https://example.test/a'),/non-public address/);
21
+ assert.equal(requests,0);
22
+ });
23
+
24
+ test('R48 URL reader revalidates every redirect and returns bounded text',async()=>{
25
+ const lookups=[];
26
+ const requests=[];
27
+ const reader=createCommanderUrlReader({
28
+ lookupFn:async host=>{
29
+ lookups.push(host);
30
+ return [{address:host==='first.example'?'8.8.8.8':'1.1.1.1',family:4}];
31
+ },
32
+ requestOnceFn:async({url,address})=>{
33
+ requests.push({url:url.href,address:address.address});
34
+ if(url.hostname==='first.example') return {status:302,headers:{location:'https://second.example/final'},body:Buffer.alloc(0)};
35
+ return {status:200,headers:{'content-type':'text/plain; charset=utf-8'},body:Buffer.from('alpha\nbeta\n','utf8')};
36
+ }
37
+ });
38
+ const result=await reader('https://first.example/start');
39
+ assert.deepEqual(lookups,['first.example','second.example']);
40
+ assert.deepEqual(requests.map(x=>x.address),['8.8.8.8','1.1.1.1']);
41
+ assert.equal(result.content,'alpha\nbeta\n');
42
+ assert.equal(result.mimeType,'text/plain');
43
+ assert.equal(result.isImage,false);
44
+ assert.match(result.sha256,/^[0-9a-f]{64}$/);
45
+ });
46
+
47
+ test('R48 URL reader rejects oversized body even when transport returns success',async()=>{
48
+ const reader=createCommanderUrlReader({
49
+ maxBytes:16,
50
+ lookupFn:async()=>[{address:'8.8.8.8',family:4}],
51
+ requestOnceFn:async()=>({status:200,headers:{'content-type':'text/plain'},body:Buffer.alloc(17)})
52
+ });
53
+ await assert.rejects(reader('https://example.test/a'),/byte limit/);
54
+ });
55
+
56
+ test('R48 URL reader returns bounded PDF bytes for the native document adapter',async()=>{
57
+ const body=Buffer.from('%PDF-1.4\n%fixture\n','ascii');
58
+ const reader=createCommanderUrlReader({
59
+ lookupFn:async()=>[{address:'8.8.8.8',family:4}],
60
+ requestOnceFn:async()=>({status:200,headers:{'content-type':'application/pdf'},body})
61
+ });
62
+ const result=await reader('https://example.test/a.pdf');
63
+ assert.equal(result.isPdf,true);
64
+ assert.equal(result.isImage,false);
65
+ assert.equal(result.mimeType,'application/pdf');
66
+ assert.equal(Buffer.from(result.content,'base64').equals(body),true);
67
+ assert.match(result.sha256,/^[0-9a-f]{64}$/);
68
+ });