scientific-method-engine 0.1.0__py3-none-any.whl

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 (45) hide show
  1. scientific_method_engine/__init__.py +5 -0
  2. scientific_method_engine/__main__.py +3 -0
  3. scientific_method_engine/cli.py +75 -0
  4. scientific_method_engine/ghidra/ClearNoReturnFunctions.java +22 -0
  5. scientific_method_engine/ghidra/CreateFunctions.java +27 -0
  6. scientific_method_engine/ghidra/ExportBoundedFlow.java +59 -0
  7. scientific_method_engine/ghidra/ExportFunctionFingerprints.java +135 -0
  8. scientific_method_engine/ghidra/ExportFunctionInventory.java +50 -0
  9. scientific_method_engine/ghidra/MergeFallThroughFragment.java +63 -0
  10. scientific_method_engine/ghidra/RecoverCitedFunctions.java +93 -0
  11. scientific_method_engine/ghidra/RepairReturningCallers.java +76 -0
  12. scientific_method_engine/ghidra/ReportCallArguments.java +65 -0
  13. scientific_method_engine/ghidra/ReportCallPaths.java +106 -0
  14. scientific_method_engine/ghidra/ReportCallSitesWithScalars.java +95 -0
  15. scientific_method_engine/ghidra/ReportCallsToRange.java +67 -0
  16. scientific_method_engine/ghidra/ReportConstantFirstArgumentCalls.java +55 -0
  17. scientific_method_engine/ghidra/ReportDataBytes.java +36 -0
  18. scientific_method_engine/ghidra/ReportDecompileMatches.java +71 -0
  19. scientific_method_engine/ghidra/ReportDecompileWindow.java +61 -0
  20. scientific_method_engine/ghidra/ReportFilePatternInMemory.java +102 -0
  21. scientific_method_engine/ghidra/ReportFirstArgumentCallSummary.java +63 -0
  22. scientific_method_engine/ghidra/ReportFunctionScalarConstants.java +56 -0
  23. scientific_method_engine/ghidra/ReportFunctionSummary.java +64 -0
  24. scientific_method_engine/ghidra/ReportInstructionContext.java +64 -0
  25. scientific_method_engine/ghidra/ReportInstructionWindow.java +36 -0
  26. scientific_method_engine/ghidra/ReportMemoryBlockForFileOffset.java +105 -0
  27. scientific_method_engine/ghidra/ReportMemoryBlocks.java +52 -0
  28. scientific_method_engine/ghidra/ReportRandomnessCandidates.java +74 -0
  29. scientific_method_engine/ghidra/ReportReferences.java +42 -0
  30. scientific_method_engine/ghidra/ReportScalarConstants.java +55 -0
  31. scientific_method_engine/ghidra/ReportStringReferences.java +102 -0
  32. scientific_method_engine/ghidra/ReportSymbolReferences.java +72 -0
  33. scientific_method_engine/x86/__init__.py +0 -0
  34. scientific_method_engine/x86/dispatch.py +51 -0
  35. scientific_method_engine/x86/image.py +172 -0
  36. scientific_method_engine/x86/machine.py +659 -0
  37. scientific_method_engine/x86/pe.py +96 -0
  38. scientific_method_engine/x86/reports.py +1259 -0
  39. scientific_method_engine/x86/trace.py +547 -0
  40. scientific_method_engine/x86/values.py +123 -0
  41. scientific_method_engine-0.1.0.dist-info/METADATA +110 -0
  42. scientific_method_engine-0.1.0.dist-info/RECORD +45 -0
  43. scientific_method_engine-0.1.0.dist-info/WHEEL +4 -0
  44. scientific_method_engine-0.1.0.dist-info/entry_points.txt +2 -0
  45. scientific_method_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,123 @@
1
+ """Small bit-vector expressions; unknown values retain their producers."""
2
+ from dataclasses import dataclass
3
+
4
+ @dataclass(frozen=True)
5
+ class Value:
6
+ bits: int
7
+ term: tuple
8
+ sources: tuple = ()
9
+
10
+ def __post_init__(self):
11
+ pending, count = [self.term], 0
12
+ while pending:
13
+ term = pending.pop()
14
+ count += 1
15
+ if count > 1024:
16
+ raise ValueError("Symbolic expression complexity limit reached; narrow the query")
17
+ if isinstance(term, tuple):
18
+ pending.extend(item for item in term if isinstance(item, tuple))
19
+
20
+ @property
21
+ def number(self):
22
+ return self.term[1] if self.term[0] == "constant" else None
23
+
24
+ def report(self):
25
+ return {"bits": self.bits, "expression": self.term, "value": self.number,
26
+ "producers": list(self.sources)}
27
+
28
+
29
+ def const(n, bits, site=None):
30
+ return Value(bits, ("constant", n % (1 << bits)), () if site is None else (site,))
31
+
32
+
33
+ def unknown(name, bits, site=None):
34
+ return Value(bits, ("unknown", name), () if site is None else (site,))
35
+
36
+
37
+ def sources(*values, site=None):
38
+ return tuple(sorted(set(x for v in values for x in v.sources) | ({site} if site is not None else set())))
39
+
40
+
41
+ def extract(v, low, bits):
42
+ if v.number is not None:
43
+ return Value(bits, const(v.number >> low, bits).term, v.sources)
44
+ if low == 0 and bits == v.bits:
45
+ return v
46
+ if v.term[0] == "join":
47
+ parts = v.term[1]
48
+ if low % 8 == 0 and bits % 8 == 0:
49
+ selected = parts[low // 8:(low + bits) // 8]
50
+ return join([Value(8, term, v.sources) for term in selected])
51
+ if v.term[0] == "extract":
52
+ original, previous_low, _, original_bits = v.term[1:]
53
+ return Value(bits, ("extract", original, previous_low + low, bits, original_bits), v.sources)
54
+ return Value(bits, ("extract", v.term, low, bits, v.bits), v.sources)
55
+
56
+
57
+ def join(parts):
58
+ bits = sum(v.bits for v in parts)
59
+ if len(parts) == 1:
60
+ # A single part is already its own value; wrapping it would nest on every partial register write.
61
+ return Value(bits, parts[0].term, sources(*parts))
62
+ if all(v.number is not None for v in parts):
63
+ n, shift = 0, 0
64
+ for v in parts:
65
+ n |= v.number << shift
66
+ shift += v.bits
67
+ return Value(bits, const(n, bits).term, sources(*parts))
68
+ if all(v.term[0] == "extract" and v.term[1] == parts[0].term[1]
69
+ and v.term[2] == i * 8 and v.bits == 8 for i, v in enumerate(parts)):
70
+ original_bits = parts[0].term[4]
71
+ term = parts[0].term[1] if bits == original_bits else ("extract", parts[0].term[1], 0, bits, original_bits)
72
+ return Value(bits, term, sources(*parts))
73
+ return Value(bits, ("join", tuple(v.term for v in parts)), sources(*parts))
74
+
75
+
76
+ def resize(v, bits, signed=False):
77
+ if bits <= v.bits:
78
+ return extract(v, 0, bits)
79
+ if v.number is not None:
80
+ n = v.number
81
+ if signed and n & (1 << (v.bits - 1)):
82
+ n -= 1 << v.bits
83
+ return Value(bits, const(n, bits).term, v.sources)
84
+ return Value(bits, ("signExtend" if signed else "zeroExtend", v.term, v.bits, bits), v.sources)
85
+
86
+
87
+ def op(name, a, b, site=None):
88
+ bits = a.bits
89
+ if b.bits != bits:
90
+ b = resize(b, bits)
91
+ origin = sources(a, b, site=site)
92
+ if a.number is not None and b.number is not None:
93
+ x, y = a.number, b.number
94
+ if name == "add": n = x + y
95
+ elif name == "sub": n = x - y
96
+ elif name == "and": n = x & y
97
+ elif name == "or": n = x | y
98
+ elif name == "xor": n = x ^ y
99
+ elif name == "shl": n = x << (y & 31)
100
+ elif name == "shr": n = x >> (y & 31)
101
+ elif name == "sar": n = (x - (1 << bits) if x & (1 << (bits - 1)) else x) >> (y & 31)
102
+ elif name == "mul": n = x * y
103
+ else: raise ValueError("Unsupported expression operation: " + name)
104
+ return Value(bits, const(n, bits).term, origin)
105
+ if name in ("sub", "xor") and a.term == b.term:
106
+ return Value(bits, const(0, bits).term, origin)
107
+ if name in ("add", "sub") and b.number is not None:
108
+ delta = b.number if name == "add" else -b.number
109
+ base = a.term
110
+ if base[0] == "offset":
111
+ delta += base[2]
112
+ base = base[1]
113
+ delta %= 1 << bits
114
+ return Value(bits, base if delta == 0 else ("offset", base, delta), origin)
115
+ return Value(bits, (name, a.term, b.term), origin)
116
+
117
+
118
+ def address_parts(v):
119
+ if v.number is not None:
120
+ return ("absolute",), v.number
121
+ if v.term[0] == "offset":
122
+ return v.term[1], v.term[2]
123
+ return v.term, 0
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.5
2
+ Name: scientific-method-engine
3
+ Version: 0.1.0
4
+ Summary: Bounded instruction-derived x86 evidence reports for segmented MZ/FBOV and PE32/i386 code.
5
+ Project-URL: Source, https://github.com/kibertoad/refurbished-dinosaurs-toolkit/tree/main/packages/scientific-method-engine
6
+ Author: kibertoad
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: capstone==5.0.7
11
+ Description-Content-Type: text/markdown
12
+
13
+ # scientific-method-engine
14
+
15
+ Bounded instruction-derived x86 evidence reports for segmented 16-bit MZ/FBOV code and
16
+ PE32/i386 code. The engine decodes instructions with Capstone, follows bounded paths and emits
17
+ `bounded-x86-v1` JSON. It never runs the original program.
18
+
19
+ ```sh
20
+ uv add --group research scientific-method-engine # or: pip install scientific-method-engine
21
+ ```
22
+
23
+ For original MZ/FBOV executables, run reports through
24
+ [`@scientific-method/executable-reader`](https://www.npmjs.com/package/@scientific-method/executable-reader). The
25
+ reader derives relocation, fixup and trampoline data from the hash-checked source and pipes a
26
+ prepared config to this engine. Running the engine directly trusts whatever relocation data the
27
+ config supplies, so use it directly only for synthetic inputs, PE32 sources and checked mappings:
28
+
29
+ ```sh
30
+ scientific-method-engine trace analysis/query.json
31
+ python -m scientific_method_engine trace analysis/query.json
32
+ ```
33
+
34
+ The package also carries the shared Ghidra headless scripts. `scientific-method-engine
35
+ ghidra-scripts` prints their directory, for Ghidra's `-scriptPath`:
36
+
37
+ ```powershell
38
+ & "$env:GHIDRA_HOME/support/analyzeHeadless.bat" $project $name -process GAME.EXE -noanalysis `
39
+ -scriptPath (scientific-method-engine ghidra-scripts) -postScript ReportReferences.java 0x1234
40
+ ```
41
+
42
+ Addresses are Ghidra addresses (`0x00401000`, or `1028:d820` for segmented programs). Report scripts
43
+ print to the analyzer log and cap their output. The scripts compile against Ghidra 12.1.
44
+
45
+ Reading code and data:
46
+
47
+ | Script | Arguments | Prints |
48
+ |---|---|---|
49
+ | `ReportInstructionContext` | one or more instruction addresses | a bounded instruction window around each |
50
+ | `ReportInstructionWindow` | address, instruction count | instructions from the address onward |
51
+ | `ReportDataBytes` | address, byte count (1..256) | the bytes at the address |
52
+ | `ReportFunctionSummary` | one or more addresses | focused decompiler output of each containing function |
53
+ | `ReportDecompileWindow` | address, first line (1-based), line count | a window of one function's decompilation |
54
+ | `ReportDecompileMatches` | address, one or more literal text patterns | decompilation lines around each match |
55
+ | `ReportMemoryBlocks` | nothing, `page <start> <count>`, or `name <exact-name>` | memory block indexes, names, ranges and sizes, never bytes |
56
+ | `ReportFilePatternInMemory` | file offset (hex), optional pattern length (default 8) | where the bytes at that file offset occur in loaded memory |
57
+ | `ReportMemoryBlockForFileOffset` | one or more file offsets (hex) | the memory block and instructions where each offset's bytes are loaded |
58
+
59
+ Finding references and calls:
60
+
61
+ | Script | Arguments | Prints |
62
+ |---|---|---|
63
+ | `ReportReferences` | one or more addresses | references to each, with the referring instruction and function |
64
+ | `ReportStringReferences` | one or more literal string fragments | strings containing a fragment and their references |
65
+ | `ReportSymbolReferences` | one or more symbol-name fragments | matching symbols and their references |
66
+ | `ReportScalarConstants` | one or more scalar values | instructions using any of them, unsigned or signed |
67
+ | `ReportFunctionScalarConstants` | function address, one or more scalar values | instructions inside one function using any of them, compared unsigned |
68
+ | `ReportCallArguments` | callee address | the three nearest pushed arguments at every direct call |
69
+ | `ReportCallSitesWithScalars` | callee address, one or more scalar values | calls whose argument setup contains a requested value |
70
+ | `ReportConstantFirstArgumentCalls` | callee address, constant | cdecl calls whose first argument is the constant |
71
+ | `ReportFirstArgumentCallSummary` | callee address | the literal first argument of every call, and calls without one |
72
+ | `ReportCallsToRange` | start address, end address (inclusive) | calls and jumps whose target lies in the range |
73
+ | `ReportCallPaths` | start function, target function, maximum depth | direct-call paths between the two |
74
+ | `ReportRandomnessCandidates` | none | references to C runtime and Windows random and timing functions |
75
+
76
+ Exporting for comparison (each writes one file and refuses to overwrite where noted):
77
+
78
+ | Script | Arguments | Writes |
79
+ |---|---|---|
80
+ | `ExportBoundedFlow` | entry, instruction limit (1..10000), output path under `analysis/original/` | instruction metadata of one bounded flow as JSON |
81
+ | `ExportFunctionInventory` | output TSV path (must not exist) | every function's start and body size |
82
+ | `ExportFunctionFingerprints` | output TSV path | per-function and per-instruction fingerprints with addresses normalized, for matching functions across versions |
83
+
84
+ Repairing the analysis (these change the Ghidra program, so run them before reports and keep the
85
+ argument lists with the evidence that justifies them):
86
+
87
+ | Script | Arguments | Changes |
88
+ |---|---|---|
89
+ | `CreateFunctions` | one or more entry addresses | creates functions at indirect-call targets Ghidra missed |
90
+ | `RecoverCitedFunctions` | file of 8-digit hex addresses, optional CSV column | disassembles and creates a function at each address inside executable memory |
91
+ | `ClearNoReturnFunctions` | one or more addresses | clears a wrong no-return flag on each containing function |
92
+ | `RepairReturningCallers` | callee entry, caller entry, verified call addresses | checks each call targets the callee from inside the caller, clears the callee's no-return flag and the calls' flow overrides, disassembles each continuation and recomputes the caller's body |
93
+ | `MergeFallThroughFragment` | parent entry, fragment entry | merges an orphan fragment reached by the parent's fall-through into the parent |
94
+
95
+ Restoration tools that build report configs can import the parsers directly, for example to
96
+ derive a PE32 source's executable sections:
97
+
98
+ ```python
99
+ from scientific_method_engine.x86.pe import pe32
100
+
101
+ sections = [s for s in pe32(data)["sections"] if s["executable"]]
102
+ ```
103
+
104
+ `scientific_method_engine.x86.pe.pe32` and `scientific_method_engine.x86.image.read_source` are
105
+ supported imports and change only in a major release. Other modules are internal.
106
+
107
+ Commands, inputs, limits and acceptance rules are in
108
+ [the bounded evidence reporter guide](https://github.com/kibertoad/refurbished-dinosaurs-toolkit/blob/main/docs/bounded-evidence-reporters.md).
109
+ The reader and engine check that they speak the same prepared-config protocol and refuse to run
110
+ otherwise.
@@ -0,0 +1,45 @@
1
+ scientific_method_engine/__init__.py,sha256=8Kee68O-FZ_rmBXLQEZoGFgNVvHdhFjs-QVYcAFJMlk,274
2
+ scientific_method_engine/__main__.py,sha256=yQP0sf-rkTO-3MOotv4blvukvNGwbJFz10hQhMsxcJM,28
3
+ scientific_method_engine/cli.py,sha256=h61KXKOdSkkXce8r-XzRkQFacoibhjsP0qI4otFR_AU,3225
4
+ scientific_method_engine/ghidra/ClearNoReturnFunctions.java,sha256=K3IaHtlz2DyvAqo1eHR9uioM3qJVbC1FiyUMMm1OLMU,817
5
+ scientific_method_engine/ghidra/CreateFunctions.java,sha256=xQ0rHDxPRaAEpOyL083946RPgVZCdBtyWgueqYyzOvk,990
6
+ scientific_method_engine/ghidra/ExportBoundedFlow.java,sha256=l4cyCnwlUYCy7vZd7ZvI2ijVDI1w5UCNSHajXlGhEjY,3868
7
+ scientific_method_engine/ghidra/ExportFunctionFingerprints.java,sha256=sRhFDbHzqHjcOiQ9uJtGc-9HHsp-dmJ_qxWnABfIeoQ,6163
8
+ scientific_method_engine/ghidra/ExportFunctionInventory.java,sha256=y5dIGUzA5AKMMqr8GVNojyJPUMXsYOlUO3_ljSKDoLU,2092
9
+ scientific_method_engine/ghidra/MergeFallThroughFragment.java,sha256=XXJ2lxkrfrJQUJqYXRgXtwBjyIm4ScpXoaoqz3dkfrY,3710
10
+ scientific_method_engine/ghidra/RecoverCitedFunctions.java,sha256=yn45v-aZIs25etm2kq3NP_HgPaTGYhWIDnDvydqjpaE,4008
11
+ scientific_method_engine/ghidra/RepairReturningCallers.java,sha256=CD7eiCHaQKXNh6fErLgjokG3hjzH5_0FyoeozmdOjmo,4038
12
+ scientific_method_engine/ghidra/ReportCallArguments.java,sha256=041GllfLHnpf_C2tu00lhJ7wMkHYSCjR1W3FitPJToU,2728
13
+ scientific_method_engine/ghidra/ReportCallPaths.java,sha256=lyAQ23bV7c0srAmZ4fm3tzOOBQ02_2uTQdjiFuuY3Ok,4418
14
+ scientific_method_engine/ghidra/ReportCallSitesWithScalars.java,sha256=77l3gylbz5D_JY3EfW9AbAcyXH_YADisBBrxlyym2-g,4237
15
+ scientific_method_engine/ghidra/ReportCallsToRange.java,sha256=Dx2zuvZZCpUAWPq5ECLAtSToZFpJRQzMoxjVMJIRvNE,2672
16
+ scientific_method_engine/ghidra/ReportConstantFirstArgumentCalls.java,sha256=vrg9UU2tPAV-pP0EaNKF56szTvkwY3jDcy-1VtP6TLk,2466
17
+ scientific_method_engine/ghidra/ReportDataBytes.java,sha256=PhspNsYKKKQL-Z9wPQUM2T1-AjaJYN5c6yhMl6XHo94,1349
18
+ scientific_method_engine/ghidra/ReportDecompileMatches.java,sha256=Rf7eDZ7SXaToB-zqGfXZo2_U1zoVk2aTvVcM2YJNj1g,2784
19
+ scientific_method_engine/ghidra/ReportDecompileWindow.java,sha256=5KHBjg07AUEyc4hkM5yiwuemqANc5dtl3MLV6OSnygk,2439
20
+ scientific_method_engine/ghidra/ReportFilePatternInMemory.java,sha256=YKxrG7ltjlls1Gk6cHTUpsq_HwUElXKbmPo2aRPuB9Q,4295
21
+ scientific_method_engine/ghidra/ReportFirstArgumentCallSummary.java,sha256=sS7vKGsYhdbUPWhFTtOBpk9kALSIlB9yyrR0mzVBe14,2629
22
+ scientific_method_engine/ghidra/ReportFunctionScalarConstants.java,sha256=7owEme1wSYRiAsiwj7DQVWEix4xgz_kKjSbuAFLv1H4,2282
23
+ scientific_method_engine/ghidra/ReportFunctionSummary.java,sha256=sVmHZ9oLJBqXjJqI8jr5WujssI9We4eFo3-AQEFFRUs,2701
24
+ scientific_method_engine/ghidra/ReportInstructionContext.java,sha256=d3OiBGzEY0RgzqDhchDWG4XUlw2zBp6y7Dv35evjLPs,2515
25
+ scientific_method_engine/ghidra/ReportInstructionWindow.java,sha256=36eIlhxsd6NWk83r5TSgRpNIw5zknE7eBha-574nLtw,1296
26
+ scientific_method_engine/ghidra/ReportMemoryBlockForFileOffset.java,sha256=_ZRBp1aJGJtmBn8P6zD_uUDfMwpCQEl5aeYo31cEkxw,4430
27
+ scientific_method_engine/ghidra/ReportMemoryBlocks.java,sha256=vopnvVO6fDRJNce3m995cX0TMu14L0Ttt3SgP6hjD5Q,2842
28
+ scientific_method_engine/ghidra/ReportRandomnessCandidates.java,sha256=IkY4U8W7AyM9lWS4ACdN2swaSnkUGaQKzlooxnE8y1U,3117
29
+ scientific_method_engine/ghidra/ReportReferences.java,sha256=wOAJDXla5F0kSo5AQkLa6uHsAb-JrwvWe9wAi19ct2I,1947
30
+ scientific_method_engine/ghidra/ReportScalarConstants.java,sha256=hjMqf5uxzpcuG-0p1EKQYj8MIPNnZhuQYcy523RFHIs,2520
31
+ scientific_method_engine/ghidra/ReportStringReferences.java,sha256=PIGT6OejS_Wuf7BdOAL7FaDmehsH1N0Gx9KEO3QYZgI,4684
32
+ scientific_method_engine/ghidra/ReportSymbolReferences.java,sha256=wFsOQ-CYKOH1UFS96vRWb0poKTZtCiqKQXA8di1S8NM,2929
33
+ scientific_method_engine/x86/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ scientific_method_engine/x86/dispatch.py,sha256=APeUDstQqFEeVurVtByDPb9N_ITD1CkAmmUOYyk8CYQ,3207
35
+ scientific_method_engine/x86/image.py,sha256=TV5FeVlsXi5j-AN_O-q6PFAj69P1V78Evo0piINBP6Q,8920
36
+ scientific_method_engine/x86/machine.py,sha256=kIbqwFILLyRW_LpEFxqkIFQfKZBs8tAlyNh8yQj40iA,34914
37
+ scientific_method_engine/x86/pe.py,sha256=NjidW638sIsmgaQONYLJOxOxDMCuIG97jxWDnIk0BCM,5494
38
+ scientific_method_engine/x86/reports.py,sha256=Jy1xsLe_j-x5VzBEnl-aZJ8GIjwUPJ2GNgU1X9t33UY,79926
39
+ scientific_method_engine/x86/trace.py,sha256=KlFl4iyHg5u-yh4JdOjjB5ZA0XV-dCQYRY-OIKymxl8,33097
40
+ scientific_method_engine/x86/values.py,sha256=Xg5eqn7Vl_7ObF9DfachLJEUg4q1WVU5RnVoMMgIaoQ,4651
41
+ scientific_method_engine-0.1.0.dist-info/METADATA,sha256=VgTTmuk-J2zId48ivIrY9axmSXtiU0x0Zlw7J20T7VA,6890
42
+ scientific_method_engine-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
43
+ scientific_method_engine-0.1.0.dist-info/entry_points.txt,sha256=KHEiOeVElyBYmlHv1eyUaFDwB2QtzonGotyoexDMe2g,78
44
+ scientific_method_engine-0.1.0.dist-info/licenses/LICENSE,sha256=9vHEhTyO3xblGldAc8euUUpxX-jF_M6k6OQ9dPlu7WU,1067
45
+ scientific_method_engine-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ scientific-method-engine = scientific_method_engine.cli:run
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Igor Savin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.