@eyeauras/insight-cli 0.0.0-stage → 0.2.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.
- package/.cli-package-build.json +1 -0
- package/LICENSE +21 -0
- package/README.md +60 -2
- package/SKILL.md +58 -0
- package/dist/src/administration-results.d.ts +1 -0
- package/dist/src/administration-results.js +84 -0
- package/dist/src/administration.d.ts +14 -0
- package/dist/src/administration.js +134 -0
- package/dist/src/archive-contract.d.ts +6 -0
- package/dist/src/archive-contract.js +108 -0
- package/dist/src/archive.d.ts +12 -0
- package/dist/src/archive.js +116 -0
- package/dist/src/arguments.d.ts +14 -0
- package/dist/src/arguments.js +62 -0
- package/dist/src/artifacts.d.ts +17 -0
- package/dist/src/artifacts.js +115 -0
- package/dist/src/bin.d.ts +2 -0
- package/dist/src/bin.js +4 -0
- package/dist/src/cli.d.ts +10 -0
- package/dist/src/cli.js +419 -0
- package/dist/src/comment-effects.d.ts +3 -0
- package/dist/src/comment-effects.js +55 -0
- package/dist/src/contexts.d.ts +115 -0
- package/dist/src/contexts.js +1078 -0
- package/dist/src/coordinator.d.ts +41 -0
- package/dist/src/coordinator.js +160 -0
- package/dist/src/cross-results.d.ts +14 -0
- package/dist/src/cross-results.js +71 -0
- package/dist/src/cross.d.ts +3 -0
- package/dist/src/cross.js +43 -0
- package/dist/src/data-effects.d.ts +3 -0
- package/dist/src/data-effects.js +103 -0
- package/dist/src/debugger-results.d.ts +4 -0
- package/dist/src/debugger-results.js +151 -0
- package/dist/src/debugger-supervisor.d.ts +1 -0
- package/dist/src/debugger-supervisor.js +136 -0
- package/dist/src/debugger.d.ts +21 -0
- package/dist/src/debugger.js +267 -0
- package/dist/src/documentation-effects.d.ts +5 -0
- package/dist/src/documentation-effects.js +56 -0
- package/dist/src/extensions.d.ts +64 -0
- package/dist/src/extensions.js +289 -0
- package/dist/src/function-effects.d.ts +4 -0
- package/dist/src/function-effects.js +112 -0
- package/dist/src/ghidra.d.ts +50 -0
- package/dist/src/ghidra.js +468 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/initial-model.d.ts +6 -0
- package/dist/src/initial-model.js +85 -0
- package/dist/src/json.d.ts +2 -0
- package/dist/src/json.js +8 -0
- package/dist/src/lifecycle.d.ts +49 -0
- package/dist/src/lifecycle.js +154 -0
- package/dist/src/memory-inputs.d.ts +8 -0
- package/dist/src/memory-inputs.js +56 -0
- package/dist/src/merge.d.ts +29 -0
- package/dist/src/merge.js +66 -0
- package/dist/src/model.d.ts +74 -0
- package/dist/src/model.js +67 -0
- package/dist/src/mutation-effects.d.ts +4 -0
- package/dist/src/mutation-effects.js +321 -0
- package/dist/src/mutation-inputs.d.ts +2 -0
- package/dist/src/mutation-inputs.js +71 -0
- package/dist/src/mutations.d.ts +4 -0
- package/dist/src/mutations.js +53 -0
- package/dist/src/native-inputs.d.ts +28 -0
- package/dist/src/native-inputs.js +182 -0
- package/dist/src/native-queries.d.ts +2 -0
- package/dist/src/native-queries.js +29 -0
- package/dist/src/projects.d.ts +31 -0
- package/dist/src/projects.js +301 -0
- package/dist/src/prototype-cli.d.ts +8 -0
- package/dist/src/prototype-cli.js +155 -0
- package/dist/src/queries.d.ts +5 -0
- package/dist/src/queries.js +84 -0
- package/dist/src/query-policy.d.ts +6 -0
- package/dist/src/query-policy.js +72 -0
- package/dist/src/query-results.d.ts +2 -0
- package/dist/src/query-results.js +224 -0
- package/dist/src/research.d.ts +184 -0
- package/dist/src/research.js +808 -0
- package/dist/src/runtime-archives.d.ts +5 -0
- package/dist/src/runtime-archives.js +127 -0
- package/dist/src/runtime-catalog.d.ts +18 -0
- package/dist/src/runtime-catalog.js +27 -0
- package/dist/src/runtime-manager.d.ts +101 -0
- package/dist/src/runtime-manager.js +444 -0
- package/dist/src/runtime-selection.d.ts +13 -0
- package/dist/src/runtime-selection.js +25 -0
- package/dist/src/saved-materials.d.ts +12 -0
- package/dist/src/saved-materials.js +196 -0
- package/dist/src/scripts.d.ts +19 -0
- package/dist/src/scripts.js +79 -0
- package/dist/src/sdk-contracts.d.ts +13 -0
- package/dist/src/sdk-contracts.js +37 -0
- package/dist/src/sdk-files.d.ts +29 -0
- package/dist/src/sdk-files.js +249 -0
- package/dist/src/sdk.d.ts +4 -0
- package/dist/src/sdk.js +125 -0
- package/dist/src/server.d.ts +46 -0
- package/dist/src/server.js +460 -0
- package/dist/src/store.d.ts +33 -0
- package/dist/src/store.js +178 -0
- package/dist/src/sync-policy.d.ts +22 -0
- package/dist/src/sync-policy.js +123 -0
- package/dist/src/transport.d.ts +23 -0
- package/dist/src/transport.js +135 -0
- package/dist/src/ui-results.d.ts +2 -0
- package/dist/src/ui-results.js +80 -0
- package/dist/src/ui.d.ts +17 -0
- package/dist/src/ui.js +88 -0
- package/dist/src/utility.d.ts +14 -0
- package/dist/src/utility.js +43 -0
- package/dist/src/version-tracking.d.ts +57 -0
- package/dist/src/version-tracking.js +200 -0
- package/dist/src/workspace.d.ts +199 -0
- package/dist/src/workspace.js +564 -0
- package/package.json +47 -4
- package/references/InsightRepositoryProbe.java +55 -0
- package/references/administration.md +34 -0
- package/references/archive.md +32 -0
- package/references/artifacts.md +47 -0
- package/references/debugger.md +40 -0
- package/references/extensions-and-sync.md +124 -0
- package/references/managed-runtime.md +116 -0
- package/references/mutations.json +1424 -0
- package/references/native/InsightApplyDocumentation.java +48 -0
- package/references/native/InsightArchivePayloads.java +36 -0
- package/references/native/InsightBlockTransfer.java +57 -0
- package/references/native/InsightEmulation.java +93 -0
- package/references/native/InsightExtensions.java +36 -0
- package/references/native/InsightFunctionDatabases.java +98 -0
- package/references/native/InsightGuiMain.java +32 -0
- package/references/native/InsightHeadlessMain.java +10 -0
- package/references/native/InsightInitialImport.java +42 -0
- package/references/native/InsightInitialJournal.java +48 -0
- package/references/native/InsightInitialPublication.java +142 -0
- package/references/native/InsightKeytool.java +16 -0
- package/references/native/InsightNativeReceipt.java +31 -0
- package/references/native/InsightPlugins.java +34 -0
- package/references/native/InsightProjectFiles.java +92 -0
- package/references/native/InsightPublicationRecovery.java +63 -0
- package/references/native/InsightPythonWheels.java +31 -0
- package/references/native/InsightResearchQueries.java +188 -0
- package/references/native/InsightRuntimeDoctor.java +33 -0
- package/references/native/InsightSdkFiles.java +142 -0
- package/references/native/InsightSdkScript.java +31 -0
- package/references/native/InsightSdkTools.java +338 -0
- package/references/native/InsightSdkTypes.java +132 -0
- package/references/native/InsightServerAdministration.java +82 -0
- package/references/native/InsightServerInit.java +62 -0
- package/references/native/InsightServerMain.java +43 -0
- package/references/native/InsightServerProbe.java +29 -0
- package/references/native/InsightSnapshotFields.java +68 -0
- package/references/native/InsightSnapshotInspect.java +189 -0
- package/references/native/InsightSnapshotPublisher.java +123 -0
- package/references/native/InsightStartDebugger.java +28 -0
- package/references/native/InsightStopDebugger.java +20 -0
- package/references/native/InsightTraceInspect.java +24 -0
- package/references/native/InsightTraceState.java +130 -0
- package/references/native/InsightTraceTools.java +114 -0
- package/references/native/InsightVersionTracking.java +91 -0
- package/references/native-workflows.md +112 -0
- package/references/queries.json +1761 -0
- package/references/query-semantics.md +96 -0
- package/references/recovery.md +53 -0
- package/references/research.md +101 -0
- package/references/setup.md +49 -0
- package/runtime/GhidraMCP-7.0.0.jar +0 -0
- package/runtime/GhidraMCP-LICENSE +202 -0
- package/runtime/GhidraMCP-NOTICE +10 -0
- package/runtime/provenance.json +11 -0
- package/skills/insight-re/SKILL.md +108 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { address } from './research.js';
|
|
2
|
+
import { object, fail } from './model.js';
|
|
3
|
+
import { verifyFunctionMutation } from './function-effects.js';
|
|
4
|
+
import { verifyComments } from './comment-effects.js';
|
|
5
|
+
import { verifyDataMutation } from './data-effects.js';
|
|
6
|
+
import { verifyDocumentation } from './documentation-effects.js';
|
|
7
|
+
import { verifyMemoryCreation } from './memory-inputs.js';
|
|
8
|
+
const equal = (actual, expected, label) => { if (actual !== expected)
|
|
9
|
+
fail('partial', `Saved ${label} does not match the requested effect.`); };
|
|
10
|
+
const normalized = (value) => address(String(value));
|
|
11
|
+
const category = (value) => '/' + String(value).replace(/^\/+|\/+$/g, '');
|
|
12
|
+
/** Effect assertions run against an immutable saved program, not the response's success flag. */
|
|
13
|
+
export async function verifyMutation(backend, snapshot, path, p, response) {
|
|
14
|
+
if (path.startsWith('/sdk/')) {
|
|
15
|
+
if (!backend.inspect)
|
|
16
|
+
fail('configuration', 'Native SDK edits require immutable saved-state inspection.');
|
|
17
|
+
return backend.inspect(snapshot, { kind: 'sdk', operation: path.slice(5), parameters: p });
|
|
18
|
+
}
|
|
19
|
+
if (path === '/create_memory_block')
|
|
20
|
+
return verifyMemoryCreation(backend, snapshot, p);
|
|
21
|
+
if (path === '/set_function_prototype') {
|
|
22
|
+
if (!backend.inspect)
|
|
23
|
+
fail('configuration', 'Prototype edits require saved-state inspection.');
|
|
24
|
+
const value = await backend.inspect(snapshot, { kind: 'function', address: p.function_address, prototype: p.prototype });
|
|
25
|
+
equal(value.exists, true, 'function presence');
|
|
26
|
+
const expected = object(value.expectedPrototype);
|
|
27
|
+
equal(value.returnType, expected.returnType, 'prototype return type');
|
|
28
|
+
if (!Array.isArray(value.parameters) || !Array.isArray(expected.parameters))
|
|
29
|
+
fail('partial', 'Saved prototype parameters are absent.');
|
|
30
|
+
const actual = value.parameters.filter(v => !v.auto);
|
|
31
|
+
equal(actual.length, expected.parameters.length, 'prototype parameter count');
|
|
32
|
+
for (let i = 0; i < actual.length; i++)
|
|
33
|
+
equal(actual[i].type, object(expected.parameters[i]).type, 'prototype parameter type');
|
|
34
|
+
const convention = p.calling_convention || expected.inlineCallingConvention;
|
|
35
|
+
if (convention === 'default') {
|
|
36
|
+
if (!['default', value.defaultCallingConvention].includes(value.callingConvention))
|
|
37
|
+
fail('partial', 'Saved prototype default convention differs from the compiler specification.');
|
|
38
|
+
}
|
|
39
|
+
else if (convention)
|
|
40
|
+
equal(value.callingConvention, convention, 'prototype convention');
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (['/import_data_types', '/apply_data_classification', '/set_global', '/set_function_this_type', '/clear_flow_and_repair'].includes(path))
|
|
44
|
+
return verifyDataMutation(backend, snapshot, path, p, response);
|
|
45
|
+
if (path === '/apply_function_documentation')
|
|
46
|
+
return verifyDocumentation(backend, snapshot, p);
|
|
47
|
+
if (['/batch_set_comments', '/clear_function_comments'].includes(path))
|
|
48
|
+
return verifyComments(backend, snapshot, path, p);
|
|
49
|
+
if (path === '/batch_apply_documentation') {
|
|
50
|
+
if (!Array.isArray(response.errors) || response.errors.length)
|
|
51
|
+
fail('partial', 'Documentation batch has incomplete steps.');
|
|
52
|
+
const comments = await verifyComments(backend, snapshot, path, p);
|
|
53
|
+
if (!backend.inspect)
|
|
54
|
+
fail('configuration', 'Documentation requires saved-state inspection.');
|
|
55
|
+
const functionState = await backend.inspect(snapshot, { kind: 'function', address: p.address, ...(p.prototype ? { prototype: p.prototype } : {}) });
|
|
56
|
+
equal(functionState.exists, true, 'documented function presence');
|
|
57
|
+
if (p.name)
|
|
58
|
+
equal(functionState.name, p.name, 'documented function name');
|
|
59
|
+
const variables = functionState.variables;
|
|
60
|
+
if (!Array.isArray(variables))
|
|
61
|
+
fail('partial', 'Saved documented variables are absent.');
|
|
62
|
+
const renames = object(p.variable_renames ?? {}), types = object(p.variable_types ?? {});
|
|
63
|
+
for (const [oldName, newName] of Object.entries(renames))
|
|
64
|
+
if (!variables.some(v => object(v).name === newName))
|
|
65
|
+
fail('partial', 'Saved variable rename is absent.');
|
|
66
|
+
for (const [name, typeName] of Object.entries(types)) {
|
|
67
|
+
const saved = variables.find(v => object(v).name === (renames[name] ?? name));
|
|
68
|
+
if (!saved)
|
|
69
|
+
fail('partial', 'Saved documented variable is absent.');
|
|
70
|
+
const wanted = await backend.inspect(snapshot, { kind: 'resolve_type', name: typeName });
|
|
71
|
+
equal(wanted.exists, true, 'documented variable type');
|
|
72
|
+
equal(object(saved).type, object(wanted.type).name, 'documented variable type');
|
|
73
|
+
}
|
|
74
|
+
if (p.prototype) {
|
|
75
|
+
const expected = object(functionState.expectedPrototype);
|
|
76
|
+
equal(functionState.returnType, expected.returnType, 'documented return type');
|
|
77
|
+
const actual = functionState.parameters;
|
|
78
|
+
if (!Array.isArray(actual) || !Array.isArray(expected.parameters))
|
|
79
|
+
fail('partial', 'Saved signature parameters are absent.');
|
|
80
|
+
equal(actual.length, expected.parameters.length, 'documented parameter count');
|
|
81
|
+
for (let i = 0; i < actual.length; i++)
|
|
82
|
+
equal(object(actual[i]).type, object(expected.parameters[i]).type, 'documented parameter type');
|
|
83
|
+
}
|
|
84
|
+
if (p.calling_convention)
|
|
85
|
+
equal(functionState.callingConvention, p.calling_convention, 'documented calling convention');
|
|
86
|
+
return { function: functionState, ...comments };
|
|
87
|
+
}
|
|
88
|
+
if (path === '/run_script_inline' || path === '/run_ghidra_script') {
|
|
89
|
+
// Output describes execution, while the snapshot captures the resulting complete program state.
|
|
90
|
+
// Scripts can have external effects, so an unknown response cannot be reconciled from DB state alone.
|
|
91
|
+
if (response.success !== true || typeof response.console_output !== 'string' && response.output_captured !== false)
|
|
92
|
+
fail('partial', 'Script execution lacks a complete successful outcome.');
|
|
93
|
+
if (!backend.inspect)
|
|
94
|
+
fail('configuration', 'Script execution requires saved-state inspection.');
|
|
95
|
+
const metadata = await backend.inspect(snapshot, { kind: 'metadata' });
|
|
96
|
+
if (typeof metadata.sha256 !== 'string' || !metadata.sha256)
|
|
97
|
+
fail('partial', 'Saved script state lacks its input identity.');
|
|
98
|
+
return { success: true, ...(typeof response.console_output === 'string' ? { output: response.console_output } : {}),
|
|
99
|
+
outputCaptured: response.output_captured !== false, ...(path === '/run_ghidra_script' ? { script: p.script_name } : {}) };
|
|
100
|
+
}
|
|
101
|
+
if (['/create_function', '/delete_function', '/clear_instruction_flow_override', '/disassemble_bytes', '/add_function_tag', '/remove_function_tag',
|
|
102
|
+
'/delete_function_tag', '/set_function_tag_comment', '/set_function_no_return', '/set_variable_type', '/set_variable_storage',
|
|
103
|
+
'/rename_variables', '/batch_rename_function_components', '/set_variables'].includes(path))
|
|
104
|
+
return verifyFunctionMutation(backend, snapshot, path, p, response);
|
|
105
|
+
if (!backend.inspect)
|
|
106
|
+
fail('configuration', 'Structural operations require saved-state inspection.');
|
|
107
|
+
const inspect = (kind, parameters = {}) => backend.inspect(snapshot, { kind, ...parameters });
|
|
108
|
+
if (path === '/run_analysis' || path === '/reanalyze') {
|
|
109
|
+
const value = await inspect('metadata');
|
|
110
|
+
equal(value.analyzed, true, 'completed analysis flag');
|
|
111
|
+
if (response.success !== true)
|
|
112
|
+
fail('partial', 'Analysis completion was not established.');
|
|
113
|
+
if (response.total_functions !== undefined)
|
|
114
|
+
equal(value.functionCount, response.total_functions, 'analyzed function count');
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
if (path === '/configure_analyzer') {
|
|
118
|
+
const value = await inspect('option', { name: p.name, group: 'Analyzers' });
|
|
119
|
+
equal(value.exists, true, 'analyzer option presence');
|
|
120
|
+
if (p.enabled !== undefined)
|
|
121
|
+
equal(value.value, String(p.enabled), 'analyzer enabled flag');
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
const type = async (name) => { const value = await inspect('type', { name }); equal(value.exists, true, 'type presence'); return object(value.type); };
|
|
125
|
+
const structure = async (name) => { const value = await type(name); if (!Array.isArray(value.fields))
|
|
126
|
+
fail('partial', 'Saved composite layout is absent.'); return value; };
|
|
127
|
+
if (['/clone_data_type', '/create_derived_type', '/rename_data_type'].includes(path)) {
|
|
128
|
+
const name = p.new_name ?? p.name ?? response.name;
|
|
129
|
+
const value = await type(name);
|
|
130
|
+
equal(value.name, name, 'type name');
|
|
131
|
+
if (path === '/clone_data_type' && p.source_type !== p.new_name) {
|
|
132
|
+
const original = await type(p.source_type);
|
|
133
|
+
equal(original.size, value.size, 'copied type size');
|
|
134
|
+
if (original.fields !== undefined)
|
|
135
|
+
equal(JSON.stringify(original.fields), JSON.stringify(value.fields), 'copied layout');
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
if (path === '/create_function_signature') {
|
|
140
|
+
const value = await type(p.name);
|
|
141
|
+
equal(value.name, p.name, 'signature type name');
|
|
142
|
+
const parameters = typeof p.parameters === 'string' ? JSON.parse(p.parameters) : p.parameters;
|
|
143
|
+
if (!Array.isArray(parameters) || !Array.isArray(value.arguments))
|
|
144
|
+
fail('partial', 'Saved function signature arguments are absent.');
|
|
145
|
+
equal(value.arguments.length, parameters.length, 'signature argument count');
|
|
146
|
+
for (let i = 0; i < parameters.length; i++) {
|
|
147
|
+
const resolved = await inspect('resolve_type', { name: object(parameters[i]).type });
|
|
148
|
+
equal(resolved.exists, true, 'signature argument type');
|
|
149
|
+
equal(value.arguments[i], object(resolved.type).name, 'signature argument type');
|
|
150
|
+
}
|
|
151
|
+
const result = await inspect('resolve_type', { name: p.return_type });
|
|
152
|
+
equal(result.exists, true, 'signature return type');
|
|
153
|
+
equal(value.returnType, object(result.type).name, 'signature return type');
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
if (path === '/create_data_type_category') {
|
|
157
|
+
const value = await inspect('category', { name: category(p.category_path) });
|
|
158
|
+
equal(value.exists, true, 'category presence');
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
if (path === '/delete_data_type') {
|
|
162
|
+
const value = await inspect('type', { name: p.type_name });
|
|
163
|
+
equal(value.exists, false, 'deleted type absence');
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
if (path === '/move_data_type_to_category') {
|
|
167
|
+
const value = await type(p.type_name);
|
|
168
|
+
equal(value.category, category(p.category_path), 'type category');
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
if (path === '/create_enum') {
|
|
172
|
+
const value = await type(p.name), members = object(value.values), expected = object(p.values);
|
|
173
|
+
equal(value.size, p.size ?? 4, 'enum size');
|
|
174
|
+
equal(Object.keys(members).length, Object.keys(expected).length, 'enum member count');
|
|
175
|
+
for (const [name, item] of Object.entries(expected)) {
|
|
176
|
+
let wanted;
|
|
177
|
+
try {
|
|
178
|
+
wanted = BigInt(String(item)).toString();
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
fail('invalid', 'Enum values must be exact integers.');
|
|
182
|
+
}
|
|
183
|
+
equal(members[name], wanted, 'enum member ' + name);
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
if (path === '/create_union' || path === '/recreate_struct') {
|
|
188
|
+
const value = await structure(p.name), fields = value.fields;
|
|
189
|
+
if (!Array.isArray(p.fields))
|
|
190
|
+
fail('invalid', 'Composite fields must be an array.');
|
|
191
|
+
equal(fields.length, p.fields.length, 'composite field count');
|
|
192
|
+
for (let i = 0; i < fields.length; i++) {
|
|
193
|
+
const wanted = object(p.fields[i]);
|
|
194
|
+
equal(fields[i].name, wanted.name, 'composite field name');
|
|
195
|
+
if (wanted.offset !== undefined && path === '/recreate_struct')
|
|
196
|
+
equal(fields[i].offset, wanted.offset, 'composite field offset');
|
|
197
|
+
}
|
|
198
|
+
if (path === '/recreate_struct' && Number(p.size ?? -1) > 0)
|
|
199
|
+
equal(value.size, p.size, 'structure size');
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
if (['/add_struct_field', '/modify_struct_field', '/remove_struct_field'].includes(path)) {
|
|
203
|
+
const value = await structure(p.struct_name), fields = value.fields;
|
|
204
|
+
const selector = String(p.field_name ?? ''), byOffset = selector.startsWith('offset:') ? Number(selector.slice(7)) : undefined;
|
|
205
|
+
const target = fields.find(f => byOffset === undefined ? f.name === (p.new_name || selector) : f.offset === byOffset);
|
|
206
|
+
if (path === '/remove_struct_field') {
|
|
207
|
+
if (fields.some(f => f.name === selector))
|
|
208
|
+
fail('partial', 'Removed field remains in the saved structure.');
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
if (!target)
|
|
212
|
+
fail('partial', 'Edited field is absent from the saved structure.');
|
|
213
|
+
if (path === '/add_struct_field' && Number(p.offset ?? -1) >= 0)
|
|
214
|
+
equal(target.offset, p.offset, 'field offset');
|
|
215
|
+
if (p.new_name)
|
|
216
|
+
equal(target.name, p.new_name, 'field name');
|
|
217
|
+
const expected = p.new_type ?? p.field_type;
|
|
218
|
+
if (expected) {
|
|
219
|
+
const resolved = await inspect('resolve_type', { name: expected });
|
|
220
|
+
equal(resolved.exists, true, 'resolved field type');
|
|
221
|
+
equal(String(target.type).replace(/\s+/g, ''), String(object(resolved.type).name).replace(/\s+/g, ''), 'field type');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
if (path === '/resize_struct') {
|
|
227
|
+
const value = await structure(p.name);
|
|
228
|
+
equal(value.size, p.new_size, 'structure size');
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
if (path === '/resolve_duplicate_type') {
|
|
232
|
+
const value = await type(p.type_name);
|
|
233
|
+
if (Number(value.size) <= 1)
|
|
234
|
+
fail('partial', 'Canonical type is still a placeholder.');
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
if (path === '/apply_data_type') {
|
|
238
|
+
// The acknowledgment carries the resolved type; the saved type must exist with that size.
|
|
239
|
+
const resolved = await inspect('resolve_type', { name: p.type_name });
|
|
240
|
+
equal(resolved.exists, true, 'resolved applied type');
|
|
241
|
+
const value = object(resolved.type);
|
|
242
|
+
if (Number(value.size) <= 0)
|
|
243
|
+
fail('partial', 'Applied type has no defined size.');
|
|
244
|
+
const observed = await inspect('data', { address: p.address });
|
|
245
|
+
equal(observed.exists, true, 'defined data presence');
|
|
246
|
+
equal(observed.type, value.name, 'applied data type');
|
|
247
|
+
return observed;
|
|
248
|
+
}
|
|
249
|
+
if (['/create_property_map', '/delete_property_map', '/set_property', '/remove_property'].includes(path)) {
|
|
250
|
+
const value = await inspect('property', { name: p.name ?? p.map, ...(p.address ? { address: p.address } : {}) });
|
|
251
|
+
equal(value.exists, path !== '/delete_property_map', 'property map presence');
|
|
252
|
+
if (path === '/set_property') {
|
|
253
|
+
equal(value.hasValue, true, 'property presence');
|
|
254
|
+
if (value.value !== null)
|
|
255
|
+
equal(value.value, String(p.value ?? ''), 'property value');
|
|
256
|
+
}
|
|
257
|
+
if (path === '/remove_property')
|
|
258
|
+
equal(value.hasValue, false, 'removed property absence');
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
if (path === '/set_program_option' || path === '/remove_program_option') {
|
|
262
|
+
const value = await inspect('option', { name: p.name, group: p.group });
|
|
263
|
+
equal(value.exists, path === '/set_program_option', 'program option presence');
|
|
264
|
+
if (path === '/set_program_option')
|
|
265
|
+
equal(value.value, String(p.value), 'program option value');
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
if (path === '/set_image_base') {
|
|
269
|
+
const value = await inspect('metadata');
|
|
270
|
+
equal(normalized(value.imageBase), normalized(p.address), 'image base');
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
if (path === '/set_memory_block') {
|
|
274
|
+
const value = await inspect('memory', { name: p.block ?? '', address: p.address ?? '' });
|
|
275
|
+
equal(value.exists, true, 'memory block presence');
|
|
276
|
+
for (const flag of ['read', 'write', 'execute', 'volatile'])
|
|
277
|
+
if (p[flag] !== undefined)
|
|
278
|
+
equal(value[flag], p[flag], 'memory ' + flag);
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
if (path === '/set_bookmark' || path === '/delete_bookmark') {
|
|
282
|
+
const value = await inspect('bookmarks', { address: p.address }), marks = value.bookmarks;
|
|
283
|
+
if (!Array.isArray(marks))
|
|
284
|
+
fail('partial', 'Saved bookmark inventory is absent.');
|
|
285
|
+
const selected = marks.filter(mark => object(mark).category === String(p.category ?? ''));
|
|
286
|
+
if (path === '/set_bookmark') {
|
|
287
|
+
if (!selected.some(mark => object(mark).comment === String(p.comment ?? '')))
|
|
288
|
+
fail('partial', 'Saved bookmark does not contain the requested comment.');
|
|
289
|
+
}
|
|
290
|
+
else if (p.category ? selected.length : marks.length)
|
|
291
|
+
fail('partial', 'Deleted bookmark remains in saved state.');
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
if (path === '/create_label' || path === '/delete_label') {
|
|
295
|
+
const edits = Array.isArray(p.labels) && p.labels.length ? p.labels : [{ address: p.address, name: p.name }];
|
|
296
|
+
const verified = [];
|
|
297
|
+
for (const item of edits) {
|
|
298
|
+
const label = object(item), value = await inspect('symbols', { address: label.address });
|
|
299
|
+
if (!Array.isArray(value.symbols))
|
|
300
|
+
fail('partial', 'Saved symbol inventory is absent.');
|
|
301
|
+
equal(value.symbols.some(s => object(s).name === label.name), path === '/create_label', 'label presence');
|
|
302
|
+
verified.push(value);
|
|
303
|
+
}
|
|
304
|
+
return { labels: verified };
|
|
305
|
+
}
|
|
306
|
+
if (path === '/rename_symbol') {
|
|
307
|
+
const value = await inspect('symbols', { name: p.new_name });
|
|
308
|
+
if (!Array.isArray(value.symbols) || !value.symbols.length)
|
|
309
|
+
fail('partial', 'Renamed symbol is absent from saved state.');
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
if (path === '/add_memory_reference' || path === '/remove_reference') {
|
|
313
|
+
const value = await inspect('references', { address: p.from_address });
|
|
314
|
+
if (!Array.isArray(value.references))
|
|
315
|
+
fail('partial', 'Saved reference inventory is absent.');
|
|
316
|
+
const found = value.references.filter(ref => normalized(object(ref).to) === normalized(p.to_address) && object(ref).operand === (p.operand_index ?? -1));
|
|
317
|
+
equal(found.length > 0, path === '/add_memory_reference', 'reference presence');
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
fail('schema', 'Mutation lacks its saved effect contract.');
|
|
321
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { object, fail, text } from './model.js';
|
|
2
|
+
import { address } from './research.js';
|
|
3
|
+
/** Validate nested domain input before reserving or dispatching a changing intent. */
|
|
4
|
+
export function mutationInputs(path, p) {
|
|
5
|
+
if (path === '/apply_data_classification') {
|
|
6
|
+
if (!['PRIMITIVE', 'STRUCTURE', 'ARRAY', 'STRING'].includes(String(p.classification)))
|
|
7
|
+
fail('invalid', 'Classification must be PRIMITIVE, STRUCTURE, ARRAY or STRING.');
|
|
8
|
+
const type = object(p.type_definition);
|
|
9
|
+
if (p.classification === 'PRIMITIVE')
|
|
10
|
+
text(type.type, 512);
|
|
11
|
+
if (p.classification === 'STRUCTURE') {
|
|
12
|
+
text(type.name, 512);
|
|
13
|
+
if (!Array.isArray(type.fields) || !type.fields.length)
|
|
14
|
+
fail('invalid', 'A structure requires fields.');
|
|
15
|
+
for (const raw of type.fields) {
|
|
16
|
+
const field = object(raw);
|
|
17
|
+
text(field.name, 512);
|
|
18
|
+
text(field.type, 512);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (p.classification === 'ARRAY') {
|
|
22
|
+
text(type.element_type ?? type.element_struct, 512);
|
|
23
|
+
if (!Number.isSafeInteger(Number(type.count)) || Number(type.count) < 1 || Number(type.count) > 1048576)
|
|
24
|
+
fail('invalid', 'Array count must be 1–1048576.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (path === '/set_global' && (!Number.isSafeInteger(p.array_length ?? 0) || Number(p.array_length ?? 0) < 0 || Number(p.array_length ?? 0) > 1048576))
|
|
28
|
+
fail('invalid', 'Global array length must be 0–1048576.');
|
|
29
|
+
if (path === '/apply_function_documentation') {
|
|
30
|
+
const document = object(p.json_body);
|
|
31
|
+
address(document.target_address);
|
|
32
|
+
for (const key of ['function_name', 'return_type', 'calling_convention', 'plate_comment'])
|
|
33
|
+
if (document[key] !== undefined && document[key] !== '')
|
|
34
|
+
text(document[key], key === 'plate_comment' ? 16000 : 512);
|
|
35
|
+
const array = (key) => { const value = document[key] ?? []; if (!Array.isArray(value))
|
|
36
|
+
fail('invalid', 'Documentation ' + key + ' must be an array.'); return value; };
|
|
37
|
+
const ordinals = new Set();
|
|
38
|
+
for (const raw of array('parameters')) {
|
|
39
|
+
const parameter = object(raw), ordinal = parameter.ordinal;
|
|
40
|
+
if (!Number.isSafeInteger(ordinal) || Number(ordinal) < 0 || Number(ordinal) > 65535 || ordinals.has(Number(ordinal)))
|
|
41
|
+
fail('invalid', 'Parameter ordinals must be unique integers from 0 to 65535.');
|
|
42
|
+
ordinals.add(Number(ordinal));
|
|
43
|
+
for (const key of ['name', 'type'])
|
|
44
|
+
if (parameter[key] !== undefined && parameter[key] !== '')
|
|
45
|
+
text(parameter[key], 512);
|
|
46
|
+
}
|
|
47
|
+
for (const key of ['comments', 'labels']) {
|
|
48
|
+
const offsets = new Set();
|
|
49
|
+
for (const raw of array(key)) {
|
|
50
|
+
const item = object(raw);
|
|
51
|
+
let offset;
|
|
52
|
+
try {
|
|
53
|
+
if (!/^\d+$/.test(String(item.relative_offset)))
|
|
54
|
+
throw Error();
|
|
55
|
+
offset = BigInt(String(item.relative_offset));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
fail('invalid', 'Relative offset must be a nonnegative integer.');
|
|
59
|
+
}
|
|
60
|
+
if (offset > 0x7fffffffffffffffn || offsets.has(offset.toString()))
|
|
61
|
+
fail('invalid', 'Documentation offsets must be unique signed 64-bit integers.');
|
|
62
|
+
offsets.add(offset.toString());
|
|
63
|
+
for (const field of key === 'labels' ? ['name'] : ['eol_comment', 'pre_comment'])
|
|
64
|
+
if (item[field] !== undefined && item[field] !== '')
|
|
65
|
+
text(item[field], field === 'name' ? 512 : 16000);
|
|
66
|
+
if (key === 'labels')
|
|
67
|
+
text(item.name, 512);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type CommandDefinition, type CommandContext } from '@eyeauras/cli-factory';
|
|
2
|
+
import { Research } from './research.js';
|
|
3
|
+
/** Concrete pinned edit leaves with durable intent and independent saved-state effect checks. */
|
|
4
|
+
export declare function mutationCommands(research: (context: CommandContext) => Research): CommandDefinition[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { command, Permission } from '@eyeauras/cli-factory';
|
|
3
|
+
import { key, flag, parsed, option, selectedOptions } from './arguments.js';
|
|
4
|
+
import { scope } from './research.js';
|
|
5
|
+
import { id, fail } from './model.js';
|
|
6
|
+
import { verifyMutation } from './mutation-effects.js';
|
|
7
|
+
import { mutationInputs } from './mutation-inputs.js';
|
|
8
|
+
const catalog = JSON.parse(readFileSync(new URL('../../references/mutations.json', import.meta.url), 'utf8'));
|
|
9
|
+
if (catalog.source !== '57e37a8bf388b1279cff46378f85be1554548f61')
|
|
10
|
+
throw Error('Installed mutation contracts differ from the pinned build.');
|
|
11
|
+
/** Concrete pinned edit leaves with durable intent and independent saved-state effect checks. */
|
|
12
|
+
export function mutationCommands(research) {
|
|
13
|
+
const groups = new Map();
|
|
14
|
+
for (const m of catalog.tools) {
|
|
15
|
+
const [group, leaf, ...rest] = m.command.split(' ');
|
|
16
|
+
if (!group || !leaf || rest.length)
|
|
17
|
+
throw Error('Invalid installed mutation contract.');
|
|
18
|
+
const parameters = m.params.map(p => ({ ...p, publicName: p.name === 'target' ? 'symbol' : p.name === 'no_return' ? 'non_returning' : p.name === 'json_body' ? 'document' : p.name }));
|
|
19
|
+
const options = parameters.map(p => option({ ...p, name: p.publicName }));
|
|
20
|
+
const children = groups.get(group) ?? [];
|
|
21
|
+
groups.set(group, children);
|
|
22
|
+
children.push(command(leaf, leaf.replaceAll('-', ' '), ({ options: o }, c) => {
|
|
23
|
+
const values = {};
|
|
24
|
+
for (const p of parameters) {
|
|
25
|
+
const input = o[key(p.publicName)];
|
|
26
|
+
if (input === undefined) {
|
|
27
|
+
if (p.required)
|
|
28
|
+
fail('invalid', `Missing --${flag(p.publicName)}.`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
values[p.name] = parsed(p, input);
|
|
32
|
+
}
|
|
33
|
+
for (const name of ['decompiler_comments', 'disassembly_comments'])
|
|
34
|
+
if (values[name] !== undefined) {
|
|
35
|
+
const comments = values[name];
|
|
36
|
+
if (!Array.isArray(comments))
|
|
37
|
+
fail('invalid', 'Comments must be an array.');
|
|
38
|
+
for (const item of comments) {
|
|
39
|
+
if (!item || typeof item !== 'object' || typeof item.comment !== 'string' || item.comment.length > 16000 || item.comment.includes('\0'))
|
|
40
|
+
fail('invalid', 'Every comment requires an address and bounded text.');
|
|
41
|
+
parsed({ name: 'address', type: 'string', required: true, semantic: 'address' }, item.address);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const selected = scope(o), request = o.requestId === undefined ? undefined : id(o.requestId);
|
|
45
|
+
mutationInputs(m.path, values);
|
|
46
|
+
if (['/set_program_option', '/remove_program_option'].includes(m.path) && values.group === 'Program Information' && values.name === 'Executable SHA256')
|
|
47
|
+
fail('binding', 'The immutable consumed input identity cannot be rewritten as a program option.');
|
|
48
|
+
return research(c).mutate(selected, m.path, values, async (_backend, response) => response, request, (backend, snapshot, response) => verifyMutation(backend, snapshot, m.path, values, response));
|
|
49
|
+
}, { permission: m.access === 'DESTRUCTIVE' || ['/recreate_struct', '/resize_struct', '/clone_data_type', '/set_global'].includes(m.path) ? 'Destructive' : Permission.Update,
|
|
50
|
+
options: [...selectedOptions, ...options, { flags: '--request-id <id>', description: 'Retrieve this changing intent after a lost response' }] }));
|
|
51
|
+
}
|
|
52
|
+
return [...groups].map(([name, children]) => command(name, `${name} changes`, children));
|
|
53
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface NativeInputs {
|
|
2
|
+
java: string;
|
|
3
|
+
ghidraHome: string;
|
|
4
|
+
mcpJar: string;
|
|
5
|
+
jars: string[];
|
|
6
|
+
classpath: string;
|
|
7
|
+
ghidraVersion: string;
|
|
8
|
+
fingerprint: string;
|
|
9
|
+
debuggerWheels: string[];
|
|
10
|
+
extensionRoots?: string[];
|
|
11
|
+
serverClasspath?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const pinnedMcpJar = "7084e2fda16dc779dd05398e48821ff3f9ace4d32757ea143b257b39689e5fbd";
|
|
14
|
+
export declare const packagedMcpJar: string;
|
|
15
|
+
/** Ghidra has its own cache/settings overrides in addition to Java's home/tmp properties. */
|
|
16
|
+
export declare const nativeJvmDirectories: (root: string) => string[];
|
|
17
|
+
export declare function moduleJars(home: string): Promise<string[]>;
|
|
18
|
+
export interface NativeRuntimeFile {
|
|
19
|
+
name: string;
|
|
20
|
+
path: string;
|
|
21
|
+
}
|
|
22
|
+
/** Installed native code/data, excluding documentation and isolated generated caches. */
|
|
23
|
+
export declare function nativeRuntimeFiles(ghidraHome: string, java: string): Promise<NativeRuntimeFile[]>;
|
|
24
|
+
export declare function nativeRuntimeFingerprint(ghidraVersion: string, files: NativeRuntimeFile[]): Promise<string>;
|
|
25
|
+
export declare function nativeInputs(values: Record<string, unknown>, directory?: string): Promise<NativeInputs>;
|
|
26
|
+
/** Compile packaged helper sources against the selected installation, never an author checkout. */
|
|
27
|
+
export declare function nativeHelpers(directory: string, inputs: NativeInputs): Promise<string>;
|
|
28
|
+
export declare const nativeExtensionArguments: (inputs: NativeInputs) => string[];
|