deadbyte-mcp 0.9.0 → 0.10.0

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 (121) hide show
  1. package/MANIFEST.SHA256 +119 -91
  2. package/README.txt +16 -11
  3. package/bench/fixtures/corpus.json +32 -13
  4. package/bench/fixtures/r36/case-map.json +8 -0
  5. package/bin/WORKER-REGISTRY.txt +3 -3
  6. package/bin/appcontainer-stage.exe +0 -0
  7. package/bin/appcontainer-stage.obj +0 -0
  8. package/bin/bootstrap-advapi32.exe +0 -0
  9. package/bin/bootstrap-advapi32.obj +0 -0
  10. package/bin/bootstrap-exitcode.exe +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.exe +0 -0
  13. package/bin/bootstrap-kernel32.obj +0 -0
  14. package/bin/child-control-probe.exe +0 -0
  15. package/bin/child-control-probe.obj +0 -0
  16. package/bin/child-control-stage.exe +0 -0
  17. package/bin/child-control-stage.obj +0 -0
  18. package/bin/contained-reverse-worker.exe +0 -0
  19. package/bin/contained-reverse-worker.obj +0 -0
  20. package/bin/contained-transform-worker.exe +0 -0
  21. package/bin/contained-transform-worker.obj +0 -0
  22. package/bin/containment-probe.exe +0 -0
  23. package/bin/containment-probe.obj +0 -0
  24. package/bin/deadbyte-contain.exe +0 -0
  25. package/bin/deadbyte-contain.obj +0 -0
  26. package/bin/deadbyte-exec.exe +0 -0
  27. package/bin/deadbyte-exec.obj +0 -0
  28. package/bin/deadbyte-process-host.exe +0 -0
  29. package/bin/deadbyte-process-host.obj +0 -0
  30. package/bin/deadbyte-tunnel-host.exe +0 -0
  31. package/bin/deadbyte-tunnel-host.obj +0 -0
  32. package/controller/README.TXT +8 -6
  33. package/controller/deadbyte-controller.ps1 +48 -2
  34. package/controller/deadbyte-process-policy.json +5 -5
  35. package/docs/ARCHITECTURE.md +1 -1
  36. package/docs/R36-CAPABILITY-EVIDENCE.md +43 -0
  37. package/docs/superpowers/plans/2026-09-18-r36-zero-loss-autonomy.md +739 -0
  38. package/docs/superpowers/specs/2026-09-18-r36-zero-loss-autonomy-design.md +640 -0
  39. package/package.json +1 -1
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/deferred-slot-operation.mjs +53 -15
  42. package/scripts/final-closure-verify.mjs +88 -39
  43. package/scripts/final-closure.mjs +63 -56
  44. package/scripts/gate-windows.ps1 +3 -3
  45. package/scripts/release-parity-tests.ps1 +9 -0
  46. package/scripts/release-parity.mjs +149 -139
  47. package/scripts/windows-gate-evidence.mjs +5 -5
  48. package/src/autonomous-context-engine.mjs +2 -1
  49. package/src/autonomous-mcp-tools.mjs +5 -1
  50. package/src/autonomous-output-contracts.mjs +4 -4
  51. package/src/autonomous-planner-contracts.mjs +36 -1
  52. package/src/autonomous-planner.mjs +25 -3
  53. package/src/autonomous-runtime.mjs +191 -21
  54. package/src/capability-benchmark-runner.mjs +86 -1
  55. package/src/capability-benchmark.mjs +45 -0
  56. package/src/capability-ledger.mjs +158 -0
  57. package/src/coding-mcp-tools.mjs +31 -17
  58. package/src/coding-output-contracts.mjs +48 -10
  59. package/src/coding-plane.mjs +45 -9
  60. package/src/coding-search.mjs +130 -47
  61. package/src/compact-gateway-mcp-tools.mjs +128 -0
  62. package/src/compact-gateway-output-contracts.mjs +16 -0
  63. package/src/deadbyte-cli.mjs +51 -28
  64. package/src/machine-fs-smoke-client.mjs +1 -1
  65. package/src/machine-fs.mjs +51 -8
  66. package/src/mcp-server.mjs +32 -9
  67. package/src/native-diff-v2.mjs +337 -0
  68. package/src/native-diff.mjs +50 -2
  69. package/src/observation-smoke-client.mjs +1 -1
  70. package/src/operator-surface.mjs +7 -1
  71. package/src/process-runtime.mjs +98 -11
  72. package/src/process-smoke-client.mjs +11 -3
  73. package/src/r36-compact-disarmed-probe.mjs +50 -0
  74. package/src/r36-full-surface-probe.mjs +56 -0
  75. package/src/remote-live-log.mjs +76 -0
  76. package/src/remote-mcp-autonomous-smoke-client.mjs +7 -3
  77. package/src/remote-mcp-coding-smoke-client.mjs +1 -1
  78. package/src/remote-mcp-r36-parity-client.mjs +175 -0
  79. package/src/remote-mcp-smoke-client.mjs +112 -56
  80. package/src/runtime-update.mjs +51 -1
  81. package/src/search-cursor.mjs +45 -0
  82. package/src/semantic-code.mjs +72 -5
  83. package/src/semantic-lsp-backend.mjs +39 -3
  84. package/src/version.mjs +1 -1
  85. package/test/autonomous-capability-r36.test.mjs +151 -0
  86. package/test/autonomous-runtime.test.mjs +2 -2
  87. package/test/capability-benchmark-r36.test.mjs +107 -0
  88. package/test/capability-benchmark-semantic-r36.test.mjs +52 -0
  89. package/test/capability-claims-r36.test.mjs +23 -0
  90. package/test/capability-ledger-r36.test.mjs +102 -0
  91. package/test/coding-catalog-consistency.test.mjs +2 -2
  92. package/test/coding-plane.test.mjs +2 -2
  93. package/test/coding-runtime.test.mjs +16 -0
  94. package/test/coding-search-r36.test.mjs +90 -0
  95. package/test/compact-catalog-r36.test.mjs +37 -0
  96. package/test/compact-gateway-r36.test.mjs +66 -0
  97. package/test/contained.test.mjs +1 -1
  98. package/test/core.test.mjs +1 -1
  99. package/test/deferred-slot-operation.test.mjs +9 -0
  100. package/test/fixtures/lsp-framed-server.mjs +2 -0
  101. package/test/helpers/autonomous-r34-fixture.mjs +5 -3
  102. package/test/multi-file-read.test.mjs +3 -0
  103. package/test/native-diff-topology-r36.test.mjs +105 -0
  104. package/test/package-boundary.test.mjs +6 -0
  105. package/test/process-longrun-r36.test.mjs +82 -0
  106. package/test/process-release-gate.test.mjs +9 -0
  107. package/test/production-docs.test.mjs +3 -3
  108. package/test/prompts-resources.test.mjs +7 -1
  109. package/test/r33-closeout-regression.test.mjs +4 -3
  110. package/test/r33-finalization.test.mjs +14 -9
  111. package/test/r36-finalization.test.mjs +104 -0
  112. package/test/r36-release-identity.test.mjs +52 -0
  113. package/test/release-version.test.mjs +18 -15
  114. package/test/remote-live-log-r36.test.mjs +37 -0
  115. package/test/remote-live-log-recovery-r36.test.mjs +41 -0
  116. package/test/remote-r24-catalog.test.mjs +13 -0
  117. package/test/remote-session-cli.test.mjs +40 -30
  118. package/test/runtime-self-update.test.mjs +58 -1
  119. package/test/semantic-lsp-backend.test.mjs +9 -0
  120. package/test/semantic-r36-rename.test.mjs +82 -0
  121. package/deadbyte-mcp-0.8.9.tgz +0 -0
@@ -0,0 +1,158 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ export const CAPABILITY_LEDGER_SCHEMA='deadbyte.capability-ledger.v1';
4
+ const AUTHORITIES=new Set(['none','process','coding','autonomous','host']);
5
+ const PROFILE_ENV='DEADBYTE_CAPABILITY_PROFILE';
6
+ const COMPACT_TOOL_LIMIT=42;
7
+
8
+ function sha256(text){return createHash('sha256').update(text,'utf8').digest('hex');}
9
+ function canonicalJson(value){
10
+ if(value===null||typeof value!=='object') return JSON.stringify(value);
11
+ if(Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
12
+ return `{${Object.keys(value).sort((a,b)=>a.localeCompare(b,'en')).map(key=>`${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
13
+ }
14
+ function nonEmpty(value,label){if(typeof value!=='string'||value.length<1) throw new Error(`${label} must be non-empty`); return value;}
15
+ function nullableRoute(value,label){if(value===null) return null; return nonEmpty(value,label);}
16
+ function normalizedEntry(raw){
17
+ if(!raw||typeof raw!=='object'||Array.isArray(raw)) throw new Error('capability entry must be an object');
18
+ const authority=nonEmpty(raw.authority,'capability authority');
19
+ if(!AUTHORITIES.has(authority)) throw new Error(`unknown capability authority: ${authority}`);
20
+ const entry={
21
+ id:nonEmpty(raw.id,'capability id'),
22
+ full_tool:nonEmpty(raw.full_tool,'capability full_tool'),
23
+ compact_tool:nullableRoute(raw.compact_tool??null,'capability compact_tool'),
24
+ compact_op:nullableRoute(raw.compact_op??null,'capability compact_op'),
25
+ runtime_flag:raw.runtime_flag===null?null:nonEmpty(raw.runtime_flag,'capability runtime_flag'),
26
+ authority,
27
+ mutating:raw.mutating===true,
28
+ receipt_class:raw.receipt_class===null?null:nonEmpty(raw.receipt_class,'capability receipt_class'),
29
+ production:raw.production===true,
30
+ host_only:raw.host_only===true
31
+ };
32
+ if((entry.compact_tool===null)!==(entry.compact_op===null)) throw new Error(`capability ${entry.id} compact route requires both tool and op`);
33
+ if(entry.host_only&&entry.authority!=='host') throw new Error(`capability ${entry.id} host_only requires host authority`);
34
+ return Object.freeze(entry);
35
+ }
36
+
37
+ export function createCapabilityLedger(entries){
38
+ if(!Array.isArray(entries)) throw new Error('capability entries must be an array');
39
+ const seen=new Set();
40
+ const normalized=entries.map(normalizedEntry);
41
+ for(const item of normalized){
42
+ if(seen.has(item.id)) throw new Error(`duplicate capability id: ${item.id}`);
43
+ seen.add(item.id);
44
+ }
45
+ normalized.sort((a,b)=>a.id.localeCompare(b.id,'en'));
46
+ const frozenEntries=Object.freeze(normalized.slice());
47
+ const body={schema:CAPABILITY_LEDGER_SCHEMA,entries:frozenEntries};
48
+ return Object.freeze({...body,ledger_sha256:sha256(canonicalJson(body))});
49
+ }
50
+
51
+ export function capabilityProfileFromEnv(env=process.env){
52
+ const value=env?.[PROFILE_ENV]??'full';
53
+ if(value!=='full'&&value!=='compact') throw new Error(`${PROFILE_ENV} must be full or compact`);
54
+ return value;
55
+ }
56
+
57
+ function toolNames(snapshot,label){
58
+ if(!snapshot||typeof snapshot!=='object'||!Array.isArray(snapshot.tools)) throw new Error(`${label} tool surface snapshot invalid`);
59
+ const names=snapshot.tools.map(tool=>tool?.name);
60
+ if(names.some(name=>typeof name!=='string'||name.length<1)) throw new Error(`${label} tool surface contains invalid name`);
61
+ return new Set(names);
62
+ }
63
+
64
+ export function assertCapabilityClosure({ledger,fullToolSnapshot,compactToolSnapshot}){
65
+ if(!ledger||ledger.schema!==CAPABILITY_LEDGER_SCHEMA||!Array.isArray(ledger.entries)) throw new Error('capability ledger invalid');
66
+ const full=toolNames(fullToolSnapshot,'full');
67
+ const compact=toolNames(compactToolSnapshot,'compact');
68
+ if(compact.size>COMPACT_TOOL_LIMIT) throw new Error(`compact surface exceeds ${COMPACT_TOOL_LIMIT}-tool limit: ${compact.size}`);
69
+ for(const capability of ledger.entries){
70
+ if(!full.has(capability.full_tool)) throw new Error(`capability ${capability.id} full tool missing: ${capability.full_tool}`);
71
+ if(capability.production&&!capability.host_only){
72
+ if(!capability.compact_tool||!capability.compact_op) throw new Error(`capability ${capability.id} compact route missing`);
73
+ if(!compact.has(capability.compact_tool)) throw new Error(`capability ${capability.id} compact tool missing: ${capability.compact_tool}`);
74
+ }
75
+ }
76
+ return true;
77
+ }
78
+
79
+ const direct=(id,tool,runtime_flag,authority,{mutating=false,receipt_class='direct',production=true,host_only=false}={})=>({id,full_tool:tool,compact_tool:tool,compact_op:'direct',runtime_flag,authority,mutating,receipt_class,production,host_only});
80
+ const gateway=(id,full_tool,compact_tool,compact_op,runtime_flag,authority,{mutating=false,receipt_class='direct',production=true}={})=>({id,full_tool,compact_tool,compact_op,runtime_flag,authority,mutating,receipt_class,production,host_only:false});
81
+
82
+ export const R36_CAPABILITY_ENTRIES=Object.freeze([
83
+ direct('evidence.verify','evidence_verify',null,'none',{receipt_class:'verification'}),
84
+ gateway('machine.list','machine_list','machine_observe','machine_list','DEADBYTE_MACHINE_RUNTIME','none',{receipt_class:'machine'}),
85
+ gateway('machine.status','machine_status','machine_observe','machine_status','DEADBYTE_MACHINE_RUNTIME','none',{receipt_class:'machine'}),
86
+ gateway('machine.ping','machine_ping','machine_observe','machine_ping','DEADBYTE_MACHINE_RUNTIME','none',{receipt_class:'machine'}),
87
+ direct('machine.revoke','machine_revoke','DEADBYTE_MACHINE_RUNTIME','host',{mutating:true,receipt_class:'machine',production:false,host_only:true}),
88
+
89
+ gateway('process.start','process_session_start','process_session','start','DEADBYTE_PROCESS_RUNTIME','process',{mutating:true,receipt_class:'process'}),
90
+ gateway('process.read','process_session_read','process_session','read','DEADBYTE_PROCESS_RUNTIME','process',{receipt_class:'process'}),
91
+ gateway('process.status','process_session_status','process_session','status','DEADBYTE_PROCESS_RUNTIME','process',{receipt_class:'process'}),
92
+ gateway('process.list','process_session_list','process_session','list','DEADBYTE_PROCESS_RUNTIME','process',{receipt_class:'process'}),
93
+ gateway('process.action','process_session_action','process_session','action','DEADBYTE_PROCESS_RUNTIME','process',{mutating:true,receipt_class:'process'}),
94
+ gateway('process.terminate','process_session_terminate','process_session','terminate','DEADBYTE_PROCESS_RUNTIME','process',{mutating:true,receipt_class:'process'}),
95
+
96
+ direct('coding.session_status','coding_session_status','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'coding'}),
97
+ direct('coding.tree','coding_tree','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'coding'}),
98
+ gateway('coding.stat','coding_stat','coding_inspect','stat','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'coding'}),
99
+ gateway('coding.search','coding_search','coding_inspect','search','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'coding'}),
100
+ gateway('coding.files_read','coding_files_read','coding_inspect','files_read','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'coding'}),
101
+ gateway('semantic.outline','coding_symbol_outline','coding_inspect','symbol_outline','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'semantic'}),
102
+ gateway('semantic.definition','coding_symbol_definition','coding_inspect','symbol_definition','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'semantic'}),
103
+ gateway('semantic.references','coding_symbol_references','coding_inspect','symbol_references','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'semantic'}),
104
+ gateway('semantic.diagnostics','coding_symbol_diagnostics','coding_inspect','symbol_diagnostics','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'semantic'}),
105
+ gateway('semantic.implementations','coding_symbol_implementations','coding_inspect','symbol_implementations','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'semantic'}),
106
+ gateway('semantic.rename_preview','coding_symbol_rename_preview','coding_inspect','symbol_rename_preview','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{receipt_class:'native_diff'}),
107
+ direct('semantic.replace','coding_symbol_replace','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'coding'}),
108
+ direct('coding.file_read','coding_file_read','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'coding'}),
109
+ direct('coding.file_write','coding_file_write','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'coding'}),
110
+ direct('coding.text_replace','coding_text_replace','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'coding'}),
111
+ direct('coding.command_run','coding_command_run','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'coding'}),
112
+ direct('coding.benchmark_run','coding_benchmark_run','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'coding'}),
113
+ direct('coding.receipt_verify','coding_receipt_verify','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'verification'}),
114
+ direct('coding.task_submit','coding_task_submit','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'task'}),
115
+ direct('coding.task_run','coding_task_run','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'task'}),
116
+ direct('coding.task_status','coding_task_status','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'task'}),
117
+ direct('coding.task_events','coding_task_events','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'task'}),
118
+ direct('coding.task_cancel','coding_task_cancel','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'task'}),
119
+ direct('coding.task_approve','coding_task_approve','DEADBYTE_CODING_RUNTIME','coding',{mutating:true,receipt_class:'task'}),
120
+ direct('coding.task_verify','coding_task_verify','DEADBYTE_CODING_RUNTIME','coding',{receipt_class:'verification'}),
121
+
122
+ gateway('machine_fs.stat','machine_fs_stat','machine_fs','stat','DEADBYTE_MACHINE_FS_RUNTIME','coding',{receipt_class:'machine_fs'}),
123
+ gateway('machine_fs.list','machine_fs_list','machine_fs','list','DEADBYTE_MACHINE_FS_RUNTIME','coding',{receipt_class:'machine_fs'}),
124
+ gateway('machine_fs.read','machine_fs_read','machine_fs','read','DEADBYTE_MACHINE_FS_RUNTIME','coding',{receipt_class:'machine_fs'}),
125
+ gateway('machine_fs.write','machine_fs_write','machine_fs','write','DEADBYTE_MACHINE_FS_RUNTIME','coding',{mutating:true,receipt_class:'machine_fs'}),
126
+ gateway('machine_fs.edit_exact','machine_fs_edit_exact','machine_fs','edit_exact','DEADBYTE_MACHINE_FS_RUNTIME','coding',{mutating:true,receipt_class:'machine_fs'}),
127
+ gateway('machine_fs.mkdir','machine_fs_mkdir','machine_fs','mkdir','DEADBYTE_MACHINE_FS_RUNTIME','coding',{mutating:true,receipt_class:'machine_fs'}),
128
+ gateway('machine_fs.move','machine_fs_move','machine_fs','move','DEADBYTE_MACHINE_FS_RUNTIME','coding',{mutating:true,receipt_class:'machine_fs'}),
129
+ gateway('machine_fs.delete','machine_fs_delete','machine_fs','delete','DEADBYTE_MACHINE_FS_RUNTIME','coding',{mutating:true,receipt_class:'machine_fs'}),
130
+
131
+ gateway('observation.search_start','machine_search_start','machine_observe','search_start','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
132
+ gateway('observation.search_page','machine_search_page','machine_observe','search_page','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
133
+ gateway('observation.search_status','machine_search_status','machine_observe','search_status','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
134
+ gateway('observation.search_cancel','machine_search_cancel','machine_observe','search_cancel','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
135
+ gateway('observation.process_list','machine_process_list','machine_observe','process_list','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
136
+ gateway('observation.system_status','machine_system_status','machine_observe','system_status','DEADBYTE_OBSERVATION_RUNTIME','none',{receipt_class:'observation'}),
137
+
138
+ direct('autonomous.session_status','autonomous_session_status','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{receipt_class:'autonomous'}),
139
+ direct('native_diff.preview','native_diff_preview','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{mutating:true,receipt_class:'native_diff'}),
140
+ direct('native_diff.apply','native_diff_apply','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{mutating:true,receipt_class:'native_diff'}),
141
+ direct('native_diff.verify','native_diff_verify','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{receipt_class:'verification'}),
142
+ direct('autonomous.goal_submit','autonomous_goal_submit','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{mutating:true,receipt_class:'autonomous'}),
143
+ direct('autonomous.goal_run','autonomous_goal_run','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{mutating:true,receipt_class:'autonomous'}),
144
+ direct('autonomous.goal_status','autonomous_goal_status','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{receipt_class:'autonomous'}),
145
+ direct('autonomous.goal_events','autonomous_goal_events','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{receipt_class:'autonomous'}),
146
+ direct('autonomous.goal_cancel','autonomous_goal_cancel','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{mutating:true,receipt_class:'autonomous'}),
147
+ direct('autonomous.goal_approve','autonomous_goal_approve','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{mutating:true,receipt_class:'autonomous'}),
148
+ direct('autonomous.goal_verify','autonomous_goal_verify','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{receipt_class:'verification'}),
149
+ direct('autonomous.release_gate','autonomous_release_gate','DEADBYTE_AUTONOMOUS_RUNTIME','autonomous',{mutating:true,receipt_class:'release'}),
150
+
151
+ direct('host.file_read','host_file_read','DEADBYTE_PRIVILEGED_POLICY','host',{receipt_class:'host',production:false,host_only:true}),
152
+ direct('host.file_write','host_file_write','DEADBYTE_PRIVILEGED_POLICY','host',{mutating:true,receipt_class:'host',production:false,host_only:true}),
153
+ direct('host.exec','host_exec','DEADBYTE_PRIVILEGED_POLICY','host',{mutating:true,receipt_class:'host',production:false,host_only:true})
154
+ ]);
155
+ export const R36_CAPABILITY_LEDGER=createCapabilityLedger(R36_CAPABILITY_ENTRIES);
156
+
157
+ export const CAPABILITY_PROFILE_ENV=PROFILE_ENV;
158
+ export const COMPACT_SURFACE_TOOL_LIMIT=COMPACT_TOOL_LIMIT;
@@ -4,7 +4,8 @@ import {
4
4
  codingBenchmarkRunOutputSchema, codingCommandRunOutputSchema,
5
5
  codingFileReadOutputSchema, codingFilesReadOutputSchema, codingFileWriteOutputSchema,
6
6
  codingReceiptVerifyOutputSchema, codingSearchOutputSchema, codingSessionStatusOutputSchema, codingStatOutputSchema,
7
- codingSymbolOutlineOutputSchema, codingSymbolDefinitionOutputSchema, codingSymbolReferencesOutputSchema, codingSymbolReplaceOutputSchema,
7
+ codingSymbolOutlineOutputSchema, codingSymbolDefinitionOutputSchema, codingSymbolReferencesOutputSchema,
8
+ codingSymbolDiagnosticsOutputSchema, codingSymbolImplementationsOutputSchema, codingSymbolRenamePreviewOutputSchema, codingSymbolReplaceOutputSchema,
8
9
  codingTaskApproveOutputSchema, codingTaskCancelOutputSchema,
9
10
  codingTaskEventsOutputSchema, codingTaskRunOutputSchema,
10
11
  codingTaskStatusOutputSchema, codingTaskSubmitOutputSchema,
@@ -67,13 +68,16 @@ export function registerCodingTools(server, { codingRuntime, taskRuntime, semant
67
68
  }, async ({ root_id, path, hash }) => { try { return result(codingStatOutputSchema, await codingRuntime.stat({ rootId: root_id, relativePath: path, hash })); } catch (error) { return fail(codingStatOutputSchema, error); } });
68
69
 
69
70
  server.registerTool('coding_search', {
70
- description: 'Deterministic literal content search using the existing coding tree/read authority.',
71
+ description: 'Deterministic bounded content/path search with literal or regex mode, signed cursor paging, and observed file identities.',
71
72
  inputSchema: z.strictObject({ root_id: rootId, path: relPath.default('.'), pattern: z.string().min(1).max(256),
72
- mode: z.literal('literal').default('literal'), case_sensitive: z.boolean().default(false),
73
- max_results: z.number().int().min(1).max(1000).default(200), max_file_bytes: z.number().int().min(1).max(4194304).default(262144) }),
73
+ target: z.enum(['content','path']).default('content'), mode: z.enum(['literal','regex']).default('literal'), case_sensitive: z.boolean().default(false),
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
+ 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) }),
74
77
  outputSchema: codingSearchOutputSchema
75
- }, async ({ root_id, path, pattern, mode, case_sensitive, max_results, max_file_bytes }) => {
76
- try { return result(codingSearchOutputSchema, await codingRuntime.search({ rootId: root_id, path, pattern, mode, caseSensitive: case_sensitive, maxResults: max_results, maxFileBytes: max_file_bytes })); }
78
+ }, async ({ root_id, path, pattern, target, mode, case_sensitive, max_results, max_file_bytes, max_scan_bytes, page_size, cursor }) => {
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 })); }
77
81
  catch (error) { return fail(codingSearchOutputSchema, error); }
78
82
  });
79
83
 
@@ -95,6 +99,24 @@ export function registerCodingTools(server, { codingRuntime, taskRuntime, semant
95
99
  outputSchema: codingSymbolReferencesOutputSchema
96
100
  }, async ({ root_id, path, line, column, max_results }) => { try { return result(codingSymbolReferencesOutputSchema, await semanticRuntime.references({ rootId: root_id, relativePath: path, line, column, maxResults: max_results })); } catch (error) { return fail(codingSymbolReferencesOutputSchema, error); } });
97
101
 
102
+ server.registerTool('coding_symbol_diagnostics', {
103
+ description: 'Return bounded normalized semantic diagnostics with target and result identities.',
104
+ inputSchema: z.strictObject({ root_id: rootId, path: relPath, max_results: z.number().int().min(1).max(500).default(200) }),
105
+ outputSchema: codingSymbolDiagnosticsOutputSchema
106
+ }, async ({ root_id, path, max_results }) => { try { const value=await semanticRuntime.diagnostics({ rootId:root_id, relativePath:path, maxResults:max_results }); return result(codingSymbolDiagnosticsOutputSchema,value,value.status==='failed'); } catch(error){ return fail(codingSymbolDiagnosticsOutputSchema,error); } });
107
+
108
+ server.registerTool('coding_symbol_implementations', {
109
+ description: 'Resolve bounded semantic implementations at a zero-based line/column. Unsupported backends report an explicit typed status.',
110
+ inputSchema: z.strictObject({ root_id:rootId, path:relPath, line:z.number().int().min(0), column:z.number().int().min(0), max_results:z.number().int().min(1).max(500).default(200) }),
111
+ outputSchema: codingSymbolImplementationsOutputSchema
112
+ }, async ({ root_id, path, line, column, max_results }) => { try { const value=await semanticRuntime.implementations({rootId:root_id,relativePath:path,line,column,maxResults:max_results}); return result(codingSymbolImplementationsOutputSchema,value,false); } catch(error){ return fail(codingSymbolImplementationsOutputSchema,error); } });
113
+
114
+ server.registerTool('coding_symbol_rename_preview', {
115
+ description: 'Preview one semantic rename as a content-addressed Native Diff. This tool never mutates source bytes.',
116
+ inputSchema: z.strictObject({ root_id:rootId, path:relPath, line:z.number().int().min(0), column:z.number().int().min(0), new_name:z.string().regex(/^[A-Za-z_$][A-Za-z0-9_$]{0,127}$/) }),
117
+ outputSchema: codingSymbolRenamePreviewOutputSchema
118
+ }, async ({ root_id, path, line, column, new_name }) => { try { const value=await semanticRuntime.renamePreview({rootId:root_id,relativePath:path,line,column,newName:new_name}); return result(codingSymbolRenamePreviewOutputSchema,value,false); } catch(error){ return fail(codingSymbolRenamePreviewOutputSchema,error); } });
119
+
98
120
  server.registerTool('coding_symbol_replace', {
99
121
  description: 'Replace one current JS/TS symbol body by symbol_id under exact file SHA-256 precondition. The commit reuses the coding plane signed surgical-edit receipt path.',
100
122
  inputSchema: z.strictObject({ root_id: rootId, path: relPath, symbol_id: hex64, new_body: z.string().max(524288), expected_sha256: hex64 }),
@@ -102,21 +124,13 @@ export function registerCodingTools(server, { codingRuntime, taskRuntime, semant
102
124
  }, async ({ root_id, path, symbol_id, new_body, expected_sha256 }) => { try { return result(codingSymbolReplaceOutputSchema, await semanticRuntime.replaceSymbol({ rootId: root_id, relativePath: path, symbolId: symbol_id, newBody: new_body, expectedSha256: expected_sha256 })); } catch (error) { return fail(codingSymbolReplaceOutputSchema, error); } });
103
125
 
104
126
  server.registerTool('coding_files_read', {
105
- description: 'Read up to 32 bounded files in one request using the same coding-policy lease and deny-path checks as coding_file_read.',
127
+ description: 'Read up to 32 bounded files under one coding lease and return an aggregate observation identity over ordered file metadata.',
106
128
  inputSchema: z.strictObject({ root_id: rootId, files: z.array(z.strictObject({ path: relPath, encoding: z.enum(['utf8','base64']).default('utf8') })).min(1).max(32),
107
129
  max_total_bytes: z.number().int().min(1).max(2097152).default(1048576) }),
108
130
  outputSchema: codingFilesReadOutputSchema
109
131
  }, async ({ root_id, files, max_total_bytes }) => {
110
- try {
111
- const values=[]; let total=0;
112
- for(const item of files){
113
- const read=await codingRuntime.fileRead({rootId:root_id,relativePath:item.path,encoding:item.encoding});
114
- total+=read.bytes;
115
- if(total>max_total_bytes) throw new Error(`coding multi-read exceeds ${max_total_bytes} byte total limit`);
116
- values.push({path:item.path,encoding:item.encoding,bytes:read.bytes,sha256:read.sha256,content:read.content});
117
- }
118
- return result(codingFilesReadOutputSchema,{status:'ok',root_id,total_bytes:total,files:values});
119
- } catch(error){ return fail(codingFilesReadOutputSchema,error); }
132
+ try { return result(codingFilesReadOutputSchema,await codingRuntime.filesRead({rootId:root_id,files,maxTotalBytes:max_total_bytes})); }
133
+ catch(error){ return fail(codingFilesReadOutputSchema,error); }
120
134
  });
121
135
 
122
136
  server.registerTool('coding_file_read', {
@@ -70,18 +70,25 @@ export const codingTreeOutputSchema = statusContract({
70
70
  export const codingStatOutputSchema = statusContract({
71
71
  statuses: ['ok', 'failed'],
72
72
  shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(),
73
- type: z.enum(['file','directory','symlink','other']).optional(), bytes: z.number().int().min(0).optional(),
74
- mtime_utc: z.string().min(1).optional(), sha256: hex64.nullable().optional() },
75
- successStatuses: ['ok'], successRequired: ['root_id','path','type','bytes','mtime_utc','sha256']
73
+ normalized_path: z.string().min(1).max(512).optional(), type: z.enum(['file','directory','symlink','other']).optional(),
74
+ bytes: z.number().int().min(0).optional(), mtime_utc: z.string().min(1).optional(), sha256: hex64.nullable().optional(),
75
+ reparse_state: z.enum(['none','symlink']).optional(), object_identity_sha256: hex64.optional(),
76
+ policy_root_id: z.string().min(1).max(64).optional(), observation_sha256: hex64.optional() },
77
+ successStatuses: ['ok'], successRequired: ['root_id','path','normalized_path','type','bytes','mtime_utc','sha256','reparse_state','object_identity_sha256','policy_root_id','observation_sha256']
76
78
  });
77
79
  export const codingSearchOutputSchema = statusContract({
78
80
  statuses: ['ok', 'failed'],
79
81
  shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(),
80
- pattern: z.string().min(1).max(256).optional(), mode: z.literal('literal').optional(), case_sensitive: z.boolean().optional(),
81
- matches: z.array(z.strictObject({ path: z.string().min(1).max(512), line: z.number().int().min(1),
82
- column: z.number().int().min(1), preview: z.string().max(512) })).max(1000).optional(),
83
- files_scanned: z.number().int().min(0).optional(), bytes_scanned: z.number().int().min(0).optional(), truncated: z.boolean().optional() },
84
- successStatuses: ['ok'], successRequired: ['root_id','path','pattern','mode','case_sensitive','matches','files_scanned','bytes_scanned','truncated']
82
+ pattern: z.string().min(1).max(256).optional(), target: z.enum(['content','path']).optional(),
83
+ mode: z.enum(['literal','regex']).optional(), case_sensitive: z.boolean().optional(),
84
+ matches: z.array(z.strictObject({ path: z.string().min(1).max(512), line: z.number().int().min(1).nullable(),
85
+ column: z.number().int().min(1).nullable(), preview: z.string().max(512), file_sha256: hex64.nullable() })).max(500).optional(),
86
+ files_scanned: z.number().int().min(0).optional(), bytes_scanned: z.number().int().min(0).optional(),
87
+ binary_files_skipped: z.number().int().min(0).optional(), truncated: z.boolean().optional(),
88
+ truncation_reason: z.enum(['tree_limit','scan_byte_limit','max_results']).nullable().optional(),
89
+ query_sha256: hex64.optional(), snapshot_sha256: hex64.optional(), page_sha256: hex64.optional(),
90
+ next_cursor: z.string().max(8192).nullable().optional() },
91
+ successStatuses: ['ok'], successRequired: ['root_id','path','pattern','target','mode','case_sensitive','matches','files_scanned','bytes_scanned','binary_files_skipped','truncated','truncation_reason','query_sha256','snapshot_sha256','page_sha256','next_cursor']
85
92
  });
86
93
  const semanticLocationSchema = z.strictObject({
87
94
  path: z.string().min(1).max(512), name: z.string().max(256), kind: z.string().min(1).max(64),
@@ -113,6 +120,36 @@ export const codingSymbolReferencesOutputSchema = statusContract({
113
120
  locations: z.array(semanticLocationSchema).max(500).optional(), truncated: z.boolean().optional() },
114
121
  successStatuses: ['ok'], successRequired: ['root_id','path','line','column','engine','locations','truncated']
115
122
  });
123
+ export const codingSymbolDiagnosticsOutputSchema = z.strictObject({
124
+ status:z.enum(['ok','unsupported','failed']), error:z.string().optional(), capability:z.string().max(64).optional(),
125
+ root_id:z.string().min(1).max(64).optional(), path:z.string().min(1).max(512).optional(), file_sha256:hex64.optional(),
126
+ engine:z.string().min(1).max(128).optional(), diagnostics:z.array(z.strictObject({message:z.string().max(4096),code:z.string().max(256),severity:z.string().min(1).max(64),location:semanticLocationSchema.optional()})).max(500).optional(),
127
+ truncated:z.boolean().optional(), result_sha256:hex64.optional()
128
+ }).superRefine((value,ctx)=>{
129
+ if(value.status==='failed'){requireFields(value,ctx,['error']);return;}
130
+ if(value.status==='unsupported'){requireFields(value,ctx,['capability','engine']);return;}
131
+ requireFields(value,ctx,['root_id','path','file_sha256','engine','diagnostics','truncated','result_sha256']);
132
+ });
133
+ export const codingSymbolImplementationsOutputSchema = z.strictObject({
134
+ status:z.enum(['ok','unsupported','failed']), error:z.string().optional(), capability:z.string().max(64).optional(),
135
+ root_id:z.string().min(1).max(64).optional(), path:z.string().min(1).max(512).optional(), line:z.number().int().min(0).optional(), column:z.number().int().min(0).optional(),
136
+ file_sha256:hex64.optional(), engine:z.string().min(1).max(128).optional(), locations:z.array(semanticLocationSchema).max(500).optional(),
137
+ truncated:z.boolean().optional(), result_sha256:hex64.optional()
138
+ }).superRefine((value,ctx)=>{
139
+ if(value.status==='failed'){requireFields(value,ctx,['error']);return;}
140
+ if(value.status==='unsupported'){requireFields(value,ctx,['capability','engine']);return;}
141
+ requireFields(value,ctx,['root_id','path','line','column','file_sha256','engine','locations','truncated','result_sha256']);
142
+ });
143
+ export const codingSymbolRenamePreviewOutputSchema = z.strictObject({
144
+ status:z.enum(['ready','unsupported','failed']), error:z.string().optional(), capability:z.string().max(64).optional(), engine:z.string().min(1).max(128).optional(),
145
+ diff_id:hex64.optional(), affected_files:z.number().int().min(1).max(32).optional(), edit_count:z.number().int().min(1).optional(), backend:z.string().min(1).max(128).optional(),
146
+ preimages:z.array(z.strictObject({path:z.string().min(1).max(512),sha256:hex64})).max(32).optional()
147
+ }).superRefine((value,ctx)=>{
148
+ if(value.status==='failed'){requireFields(value,ctx,['error']);return;}
149
+ if(value.status==='unsupported'){requireFields(value,ctx,['capability','engine']);return;}
150
+ requireFields(value,ctx,['diff_id','affected_files','edit_count','backend','preimages']);
151
+ });
152
+
116
153
  export const codingSymbolReplaceOutputSchema = statusContract({
117
154
  statuses: ['committed','failed'],
118
155
  shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(), previous_sha256: hex64.optional(),
@@ -125,8 +162,9 @@ export const codingSymbolReplaceOutputSchema = statusContract({
125
162
  export const codingFilesReadOutputSchema = statusContract({
126
163
  statuses: ['ok','failed'],
127
164
  shape: { root_id: z.string().min(1).max(64).optional(), total_bytes: z.number().int().min(0).optional(),
128
- files: z.array(z.strictObject({ path: z.string().min(1).max(512), encoding: z.enum(['utf8','base64']), bytes: z.number().int().min(0), sha256: hex64, content: z.string() })).max(32).optional() },
129
- successStatuses: ['ok'], successRequired: ['root_id','total_bytes','files']
165
+ files: z.array(z.strictObject({ path: z.string().min(1).max(512), encoding: z.enum(['utf8','base64']), bytes: z.number().int().min(0), sha256: hex64, content: z.string() })).max(32).optional(),
166
+ observation_sha256: hex64.optional() },
167
+ successStatuses: ['ok'], successRequired: ['root_id','total_bytes','files','observation_sha256']
130
168
  });
131
169
  export const codingFileReadOutputSchema = statusContract({
132
170
  statuses: ['ok', 'failed'],
@@ -9,6 +9,7 @@ import { decodeUtf8Strict } from './text-policy.mjs';
9
9
  import { isInside, validatePortableRelativePath } from './path-policy.mjs';
10
10
  import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
11
11
  import { createCodingSearch } from './coding-search.mjs';
12
+ import { createSearchCursorCodec } from './search-cursor.mjs';
12
13
 
13
14
  export const CODING_POLICY_SCHEMA = 'deadbyte.coding-policy.v1';
14
15
  export const CODING_LEASE_SCHEMA = 'deadbyte.coding-lease.v1';
@@ -613,18 +614,52 @@ export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
613
614
  async function statPath({ rootId, relativePath = '.', hash = false }) {
614
615
  const lease = await assertCodingArmed(policy, rootId);
615
616
  const root = getRoot(policy, rootId, 'read');
616
- let resolved = root.real_path;
617
+ let lexical = root.real_path;
617
618
  let reported = '.';
618
619
  if (relativePath !== '.') {
619
- const found = await resolveCodingExisting(policy, lease, rootId, relativePath, 'read');
620
- resolved = found.resolved;
620
+ validatePortableRelativePath(relativePath);
621
+ if (!lease.root_ids.includes(rootId)) throw new Error(`coding root '${rootId}' is outside active lease`);
622
+ assertNotDenied(root, relativePath);
623
+ lexical = path.resolve(root.real_path, relativePath);
624
+ if (!isInside(root.real_path, lexical)) throw new Error('coding stat path escapes allowed root');
621
625
  reported = relativePath.replaceAll('\\', '/');
622
626
  }
623
- const info = await lstat(resolved);
627
+ const info = await lstat(lexical);
624
628
  const type = info.isFile() ? 'file' : info.isDirectory() ? 'directory' : info.isSymbolicLink() ? 'symlink' : 'other';
625
- const digest = hash && type === 'file' ? await sha256File(resolved) : null;
626
- return { status: 'ok', root_id: rootId, path: reported, type, bytes: type === 'file' ? info.size : 0,
627
- mtime_utc: info.mtime.toISOString(), sha256: digest };
629
+ const reparseState = info.isSymbolicLink() ? 'symlink' : 'none';
630
+ if (type !== 'symlink') {
631
+ const resolved = await realpath(lexical);
632
+ if (!isInside(root.real_path, resolved) && resolved !== root.real_path) throw new Error('coding stat path resolves outside allowed root');
633
+ }
634
+ const contentSha = type === 'file' ? await sha256File(lexical) : null;
635
+ const objectIdentitySha = sha256Object({
636
+ schema:'deadbyte.coding-object-identity.v1',type,bytes:type==='file'?info.size:0,
637
+ content_sha256:contentSha,device:Number(info.dev),inode:Number(info.ino),reparse_state:reparseState
638
+ });
639
+ const result = {
640
+ status:'ok',root_id:rootId,path:reported,normalized_path:reported,type,
641
+ bytes:type==='file'?info.size:0,mtime_utc:info.mtime.toISOString(),
642
+ sha256:hash&&type==='file'?contentSha:null,reparse_state:reparseState,
643
+ object_identity_sha256:objectIdentitySha,policy_root_id:rootId
644
+ };
645
+ return {...result,observation_sha256:sha256Object({schema:'deadbyte.coding-stat-observation.v1',...result})};
646
+ }
647
+
648
+ async function filesRead({ rootId, files, maxTotalBytes = 1048576 }) {
649
+ if (!Array.isArray(files) || files.length < 1 || files.length > 32) throw new Error('coding multi-read requires 1-32 files');
650
+ if (!Number.isInteger(maxTotalBytes) || maxTotalBytes < 1 || maxTotalBytes > 2097152) throw new Error('coding multi-read max_total_bytes must be in [1, 2097152]');
651
+ const values=[]; let total=0;
652
+ for(const item of files){
653
+ if(!item||typeof item.path!=='string'||!['utf8','base64'].includes(item.encoding??'utf8')) throw new Error('coding multi-read file entry invalid');
654
+ const encoding=item.encoding??'utf8';
655
+ const read=await fileRead({rootId,relativePath:item.path,encoding});
656
+ total+=read.bytes;
657
+ if(total>maxTotalBytes) throw new Error(`coding multi-read exceeds ${maxTotalBytes} byte total limit`);
658
+ values.push({path:item.path,encoding,bytes:read.bytes,sha256:read.sha256,content:read.content});
659
+ }
660
+ const identity=values.map(item=>({path:item.path,encoding:item.encoding,bytes:item.bytes,sha256:item.sha256}));
661
+ return {status:'ok',root_id:rootId,total_bytes:total,files:values,
662
+ observation_sha256:sha256Object({schema:'deadbyte.coding-files-read-observation.v1',root_id:rootId,files:identity})};
628
663
  }
629
664
 
630
665
  async function fileState({ rootId, relativePath }) {
@@ -651,7 +686,8 @@ export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
651
686
  return verifyCodingReceiptFile(policy, verifyKeyPath, receiptId);
652
687
  }
653
688
 
654
- const search = createCodingSearch({ tree, fileRead });
655
- return Object.freeze({ policy, sessionStatus, tree, stat: statPath, search, fileRead, fileWrite, fileDelete, textReplace, commandRun, benchmarkRun, receiptVerify,
689
+ const cursorCodec = createSearchCursorCodec({ signingKeyPath, verifyKeyPath });
690
+ const search = createCodingSearch({ tree, fileRead, cursorCodec });
691
+ return Object.freeze({ policy, sessionStatus, tree, stat: statPath, search, filesRead, fileRead, fileWrite, fileDelete, textReplace, commandRun, benchmarkRun, receiptVerify,
656
692
  fileState, verifyReceiptById: receiptVerify, verifyReceiptDetailed, requestSha });
657
693
  }
@@ -1,55 +1,138 @@
1
- export function createCodingSearch({ tree, fileRead }) {
2
- if (typeof tree !== 'function' || typeof fileRead !== 'function') {
3
- throw new Error('coding search requires tree and fileRead');
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from './canonical-json.mjs';
3
+ import { decodeUtf8Strict } from './text-policy.mjs';
4
+
5
+ const sha256Object=value=>createHash('sha256').update(canonicalJson(value),'utf8').digest('hex');
6
+ const sha256Bytes=bytes=>createHash('sha256').update(bytes).digest('hex');
7
+
8
+ function validateInt(value,name,min,max){
9
+ if(!Number.isInteger(value)||value<min||value>max) throw new Error(`${name} must be in [${min}, ${max}]`);
10
+ }
11
+ function compileMatcher({pattern,mode,caseSensitive}){
12
+ if(typeof pattern!=='string'||pattern.length<1||pattern.length>256) throw new Error('coding search pattern must be 1-256 characters');
13
+ if(mode==='literal'){
14
+ const needle=caseSensitive?pattern:pattern.toLowerCase();
15
+ return {
16
+ all(text){
17
+ const haystack=caseSensitive?text:text.toLowerCase();
18
+ const out=[]; let from=0;
19
+ while(from<=haystack.length){
20
+ const index=haystack.indexOf(needle,from);
21
+ if(index<0) break;
22
+ out.push({index,length:Math.max(1,pattern.length)});
23
+ from=index+Math.max(1,pattern.length);
24
+ }
25
+ return out;
26
+ },
27
+ path(value){return (caseSensitive?value:value.toLowerCase()).includes(needle);}
28
+ };
29
+ }
30
+ if(mode!=='regex') throw new Error('coding search mode must be literal or regex');
31
+ let regex;
32
+ try{regex=new RegExp(pattern,caseSensitive?'gu':'giu');}
33
+ catch(error){throw new Error(`coding search regex invalid: ${error.message}`);}
34
+ return {
35
+ all(text){
36
+ const out=[]; regex.lastIndex=0; let match;
37
+ while((match=regex.exec(text))!==null){
38
+ out.push({index:match.index,length:Math.max(1,match[0].length)});
39
+ if(match[0].length===0) regex.lastIndex+=1;
40
+ }
41
+ return out;
42
+ },
43
+ path(value){regex.lastIndex=0;return regex.test(value);}
44
+ };
45
+ }
46
+ function lineColumn(text,index){
47
+ let line=1,column=1;
48
+ for(let i=0;i<index;i+=1){if(text.charCodeAt(i)===10){line+=1;column=1;}else column+=1;}
49
+ return {line,column};
50
+ }
51
+ function preview(text,index,length){
52
+ const start=Math.max(0,index-48); const end=Math.min(text.length,index+Math.max(length,1)+112);
53
+ return text.slice(start,end);
54
+ }
55
+ function normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize}){
56
+ return {
57
+ schema:'deadbyte.coding-search-query.v2',root_id:rootId,path,target,mode,pattern,
58
+ case_sensitive:caseSensitive,max_results:maxResults,max_file_bytes:maxFileBytes,
59
+ max_scan_bytes:maxScanBytes,page_size:pageSize
60
+ };
61
+ }
62
+
63
+ export function createCodingSearch({tree,fileRead,cursorCodec}){
64
+ if(typeof tree!=='function'||typeof fileRead!=='function'||!cursorCodec||typeof cursorCodec.encode!=='function'||typeof cursorCodec.decode!=='function'){
65
+ throw new Error('coding search requires tree, fileRead, and cursorCodec');
4
66
  }
5
67
  return async function search({
6
- rootId, path = '.', pattern, mode = 'literal', caseSensitive = false,
7
- maxResults = 200, maxFileBytes = 262144
8
- }) {
9
- if (mode !== 'literal') throw new Error('coding search mode must be literal');
10
- if (typeof pattern !== 'string' || pattern.length < 1 || pattern.length > 256) {
11
- throw new Error('coding search pattern must be 1-256 characters');
12
- }
13
- if (!Number.isInteger(maxResults) || maxResults < 1 || maxResults > 1000) {
14
- throw new Error('max_results must be in [1, 1000]');
15
- }
16
- if (!Number.isInteger(maxFileBytes) || maxFileBytes < 1 || maxFileBytes > 4 * 1024 * 1024) {
17
- throw new Error('max_file_bytes out of range');
68
+ rootId,path='.',pattern,target='content',mode='literal',caseSensitive=false,
69
+ maxResults=200,maxFileBytes=262144,maxScanBytes=4*1024*1024,pageSize=100,cursor=null
70
+ }){
71
+ if(!['content','path'].includes(target)) throw new Error('coding search target must be content or path');
72
+ validateInt(maxResults,'max_results',1,1000);
73
+ validateInt(maxFileBytes,'max_file_bytes',1,4*1024*1024);
74
+ validateInt(maxScanBytes,'max_scan_bytes',1,64*1024*1024);
75
+ validateInt(pageSize,'page_size',1,500);
76
+ const matcher=compileMatcher({pattern,mode,caseSensitive});
77
+ const query=normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize});
78
+ const querySha=sha256Object(query);
79
+ let decodedCursor=null;
80
+ if(cursor!==null){
81
+ decodedCursor=await cursorCodec.decode(cursor);
82
+ if(decodedCursor.query_sha256!==querySha) throw new Error('coding search cursor query mismatch');
18
83
  }
19
- const listed = await tree({ rootId, path, maxDepth: 12, maxEntries: 2000 });
20
- const files = listed.entries
21
- .filter(entry => entry.type === 'file' && entry.bytes <= maxFileBytes)
22
- .map(entry => entry.path)
23
- .sort((a, b) => a.localeCompare(b));
24
- const needle = caseSensitive ? pattern : pattern.toLowerCase();
25
- const matches = [];
26
- let filesScanned = 0;
27
- let bytesScanned = 0;
28
- for (const relativePath of files) {
29
- const item = await fileRead({ rootId, relativePath, encoding: 'utf8' }).catch(() => null);
30
- if (!item) continue;
31
- filesScanned += 1;
32
- bytesScanned += item.bytes;
33
- const haystack = caseSensitive ? item.content : item.content.toLowerCase();
34
- const index = haystack.indexOf(needle);
35
- if (index < 0) continue;
36
- const before = item.content.slice(0, index);
37
- const line = before.split('\n').length;
38
- const lastNewline = before.lastIndexOf('\n');
39
- const column = index - lastNewline;
40
- matches.push({
41
- path: relativePath,
42
- line,
43
- column,
44
- preview: item.content.slice(index, index + Math.min(160, pattern.length + 120))
45
- });
46
- if (matches.length >= maxResults) break;
84
+
85
+ const listed=await tree({rootId,path,maxDepth:12,maxEntries:2000});
86
+ const entries=listed.entries.filter(entry=>entry.type==='file').sort((a,b)=>a.path.localeCompare(b.path,'en'));
87
+ const matches=[]; const snapshotEntries=[];
88
+ let filesScanned=0,bytesScanned=0,binaryFilesSkipped=0;
89
+ let truncated=listed.truncated===true;
90
+ let truncationReason=listed.truncated===true?'tree_limit':null;
91
+
92
+ if(target==='path'){
93
+ for(const entry of entries){
94
+ snapshotEntries.push({path:entry.path,bytes:entry.bytes,type:'file'});
95
+ if(matcher.path(entry.path)){
96
+ matches.push({path:entry.path,line:null,column:null,preview:entry.path,file_sha256:null});
97
+ if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
98
+ }
99
+ }
100
+ } else {
101
+ for(const entry of entries){
102
+ if(entry.bytes>maxFileBytes) continue;
103
+ if(bytesScanned+entry.bytes>maxScanBytes){truncated=true;truncationReason='scan_byte_limit';break;}
104
+ let item;
105
+ try{item=await fileRead({rootId,relativePath:entry.path,encoding:'base64'});}catch{continue;}
106
+ const raw=Buffer.from(item.content,'base64');
107
+ filesScanned+=1; bytesScanned+=item.bytes;
108
+ const fileSha=item.sha256??sha256Bytes(raw);
109
+ snapshotEntries.push({path:entry.path,bytes:item.bytes,sha256:fileSha});
110
+ let text;
111
+ try{text=decodeUtf8Strict(raw);}catch{binaryFilesSkipped+=1;continue;}
112
+ const found=matcher.all(text);
113
+ for(const hit of found){
114
+ const lc=lineColumn(text,hit.index);
115
+ matches.push({path:entry.path,line:lc.line,column:lc.column,preview:preview(text,hit.index,hit.length),file_sha256:fileSha});
116
+ if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
117
+ }
118
+ if(matches.length>=maxResults) break;
119
+ }
47
120
  }
121
+
122
+ const snapshotSha=sha256Object({schema:'deadbyte.coding-search-snapshot.v2',query_sha256:querySha,entries:snapshotEntries});
123
+ if(decodedCursor&&decodedCursor.snapshot_sha256!==snapshotSha) throw new Error('coding search cursor stale snapshot mismatch');
124
+ const offset=decodedCursor?.offset??0;
125
+ if(offset>matches.length) throw new Error('coding search cursor offset exceeds retained results');
126
+ const pageMatches=matches.slice(offset,offset+pageSize);
127
+ const nextOffset=offset+pageMatches.length;
128
+ const hasMore=nextOffset<matches.length;
129
+ const nextCursor=hasMore?await cursorCodec.encode({query_sha256:querySha,snapshot_sha256:snapshotSha,offset:nextOffset}):null;
130
+ const pageSha=sha256Object({schema:'deadbyte.coding-search-page.v2',query_sha256:querySha,snapshot_sha256:snapshotSha,offset,matches:pageMatches});
48
131
  return {
49
- status: 'ok', root_id: rootId, path, pattern, mode,
50
- case_sensitive: caseSensitive, matches,
51
- files_scanned: filesScanned, bytes_scanned: bytesScanned,
52
- truncated: matches.length >= maxResults || listed.truncated
132
+ status:'ok',root_id:rootId,path,pattern,target,mode,case_sensitive:caseSensitive,
133
+ matches:pageMatches,files_scanned:filesScanned,bytes_scanned:bytesScanned,binary_files_skipped:binaryFilesSkipped,
134
+ truncated:truncated||hasMore,truncation_reason:truncationReason,query_sha256:querySha,snapshot_sha256:snapshotSha,
135
+ page_sha256:pageSha,next_cursor:nextCursor
53
136
  };
54
137
  };
55
138
  }