SNAPKITTYWEST commited on
Commit
9abace2
·
verified ·
1 Parent(s): d42608e

push from SNAPKITTYWEST/sovereign-engine-v2

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +12 -0
  2. .gitignore +21 -0
  3. ARCHITECTURE.md +206 -0
  4. LICENSE.tri +48 -0
  5. PRODUCTION_HARDENING.md +2149 -0
  6. README.md +511 -0
  7. SPRINT_PLAN.md +71 -0
  8. docs/CONFIGURATION.md +391 -0
  9. docs/GETTING_STARTED.md +242 -0
  10. ide/CMakeLists.txt +92 -0
  11. ide/native/bridge/bridge.h +140 -0
  12. ide/native/bridge/bridge_http.c +485 -0
  13. ide/native/bridge/example.c +86 -0
  14. ide/native/bridge/test_keys.c +90 -0
  15. ide/native/bridge/test_tools.c +84 -0
  16. ide/native/chat/pipe_client.c +105 -0
  17. ide/native/chat/pipe_client.h +18 -0
  18. ide/native/chat/protocol.c +17 -0
  19. ide/native/chat/protocol.h +42 -0
  20. ide/native/core/arena.c +68 -0
  21. ide/native/core/arena.h +28 -0
  22. ide/native/core/errors.h +27 -0
  23. ide/native/core/events.c +51 -0
  24. ide/native/core/events.h +49 -0
  25. ide/native/core/strings.c +73 -0
  26. ide/native/core/strings.h +22 -0
  27. ide/native/core/threading.h +31 -0
  28. ide/native/editor/buffer.c +482 -0
  29. ide/native/editor/buffer.h +27 -0
  30. ide/native/editor/document.c +151 -0
  31. ide/native/editor/document.h +26 -0
  32. ide/native/editor/editor_view.cpp +613 -0
  33. ide/native/editor/editor_view.h +27 -0
  34. ide/native/fcl/evaluator.c +54 -0
  35. ide/native/fcl/evaluator.h +22 -0
  36. ide/native/fcl/parser.h +44 -0
  37. ide/native/git/repository.c +101 -0
  38. ide/native/git/repository.h +20 -0
  39. ide/native/graphics/d2d_renderer.cpp +90 -0
  40. ide/native/graphics/d2d_renderer.h +17 -0
  41. ide/native/lsp/client.c +422 -0
  42. ide/native/lsp/client.h +40 -0
  43. ide/native/main.c +25 -0
  44. ide/native/platform/windows/application.c +70 -0
  45. ide/native/platform/windows/application.h +15 -0
  46. ide/native/platform/windows/shell.c +681 -0
  47. ide/native/platform/windows/shell.h +9 -0
  48. ide/native/platform/windows/window.c +175 -0
  49. ide/native/platform/windows/window.h +10 -0
  50. ide/native/terminal/conpty.c +149 -0
.env.example ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SOVEREIGN ENGINE CONFIGURATION
2
+ # Copy this to .env and add your keys
3
+
4
+ # OpenRouter API Key (free credits - rotate daily)
5
+ # Get from: https://openrouter.ai/keys
6
+ OPENROUTER_API_KEY=sk-or-v1-YOUR_KEY_HERE
7
+
8
+ # Ollama API Key (if using cloud Ollama, otherwise leave blank for local)
9
+ OLLAMA_API_KEY=
10
+
11
+ # Ollama Base URL (default: local)
12
+ OLLAMA_BASE_URL=http://localhost:11434
.gitignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.so
9
+ *.o
10
+ *.obj
11
+ .env
12
+ .env.local
13
+ *.worm
14
+ *.bin
15
+ /checkpoints/
16
+ /continuity/
17
+ .vscode/
18
+ .idea/
19
+ sovereign_gate/authority_sk.pem
20
+ sovereign_gate/.node_sk
21
+ sovereign_gate/*.pem.backup
ARCHITECTURE.md ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SOVEREIGN PYTHON LLM ENGINE
2
+
3
+ **A production-grade LLM agent framework with custom MoE routing, binary WORM storage, four-paradigm continuity, and native IPC — generated from a single DSL prompt.**
4
+
5
+ ---
6
+
7
+ ## Origin
8
+
9
+ This entire codebase was generated from scratch in a single prompt using the **HyperKittyConstraintDSL** — a deterministic constraint specification that orchestrates multi-agent code generation with proof-backed output.
10
+
11
+ One DSL. One prompt. Three coordinated agents. **16,784 lines of machine code.**
12
+
13
+ ```xml
14
+ <HyperKittyConstraintDSL version="1.0">
15
+ <Meta>
16
+ <System>HK-OS</System>
17
+ <Mode>DETERMINISTIC-CONSTRAINT-BUILD</Mode>
18
+ <Output>PROOF_BACKED_ARTIFACT</Output>
19
+ </Meta>
20
+
21
+ <BooleanKernel>
22
+ <Primitive name="NAND">NAND(a,b)=1-ab</Primitive>
23
+ <Derived name="NOT">NAND(x,x)</Derived>
24
+ <Derived name="AND">NAND(NAND(a,b),NAND(a,b))</Derived>
25
+ <Derived name="OR">NAND(NAND(a,a),NAND(b,b))</Derived>
26
+ <Derived name="IMPLIES">OR(NOT(a),b)</Derived>
27
+ <Derived name="EQUAL">AND(IMPLIES(a,b),IMPLIES(b,a))</Derived>
28
+ </BooleanKernel>
29
+
30
+ <GlyphTypeSystem>
31
+ <Unit symbol="🧠" name="Cognition"/>
32
+ <Unit symbol="📚" name="Knowledge"/>
33
+ <Unit symbol="🔍" name="Search"/>
34
+ <Unit symbol="⚙" name="Transformation"/>
35
+ <Unit symbol="⚖" name="Constraint"/>
36
+ <Unit symbol="🔐" name="Proof"/>
37
+ <Unit symbol="🌐" name="Interface"/>
38
+ </GlyphTypeSystem>
39
+
40
+ <AgentModel>
41
+ <Invariant>active(I) => trusted(I)</Invariant>
42
+ <Invariant>entropy(I) <= 0.20</Invariant>
43
+ </AgentModel>
44
+
45
+ <QuantumConstraintLayer>
46
+ <Entropy>
47
+ <Bound>H <= 0.20</Bound>
48
+ </Entropy>
49
+ </QuantumConstraintLayer>
50
+
51
+ <ProofOutput>
52
+ <ProofStatus>PROOF_TRUE</ProofStatus>
53
+ </ProofOutput>
54
+
55
+ <FinalState>
56
+ <Artifact>PYTHON_C_BRIDGE_IR</Artifact>
57
+ <BinaryOutput>BBSTRING_MACHINEC_CODE</BinaryOutput>
58
+ <Orchestration>THREE_AGENT_WORKFLOW</Orchestration>
59
+ </FinalState>
60
+ </HyperKittyConstraintDSL>
61
+ ```
62
+
63
+ The DSL defines:
64
+ - **Boolean kernel** — All logic gates derived from NAND (universal gate)
65
+ - **Glyph type system** — Semantic types for agent roles
66
+ - **Agent invariants** — Trust and entropy constraints enforced at build time
67
+ - **DAG structure** — Acyclic routing graph validated before code generation
68
+ - **Proof output** — Cryptographic hash commitment over all artifacts
69
+
70
+ ---
71
+
72
+ ## Architecture
73
+
74
+ ```
75
+ ┌─────────────────────────────────────────────────────────┐
76
+ │ SOVEREIGN ENGINE │
77
+ ├──────────────┬──────────────┬───────────────────────────┤
78
+ │ Routing │ Continuity │ Machine Code Layer │
79
+ │ │ │ │
80
+ │ RegexParser │ EnvState │ BytecodeAssembler │
81
+ │ ASTBuilder │ SeedChain │ MarshalCodec │
82
+ │ SymbolicGr. │ InodeState │ BinaryIR │
83
+ │ JordanTrans │ SharedMem │ VMExecutor │
84
+ │ JacobianLen │ Manager │ MachineCodeGen (x86-64) │
85
+ │ Constraints │ │ CTypesBridge │
86
+ │ SparseActiv │ │ DSLValidator │
87
+ │ NANDFilter │ │ SovereignMachine │
88
+ │ Dispatch │ │ │
89
+ │ MergeOutput │ ├───────────────────────────┤
90
+ │ JordanMoE │ │ Native ASM (NASM x86-64) │
91
+ │ │ │ │
92
+ ├──────────────┤ │ sovereign_runtime.asm │
93
+ │ Tools (34) │ │ qra_tensor.asm │
94
+ │ │ │ ipc_dispatcher.asm │
95
+ │ filesystem │ │ nand_kernel.asm │
96
+ │ code │ │ jordan_blocks.asm │
97
+ │ git │ │ entropy_gate.asm │
98
+ │ database │ │ sovereign_link.asm │
99
+ │ documents ├──────────────┤ │
100
+ │ web │ IPC Layer │ │
101
+ │ embeddings │ │ │
102
+ │ audio │ OpcodeReg │ │
103
+ │ pytorch │ IPCRouter │ │
104
+ │ │ ipc_core.c │ │
105
+ ├──────────────┼──────────────┼───────────────────────────┤
106
+ │ Core │ Agents │ Daemon │
107
+ │ │ │ │
108
+ │ WORMFile │ ReActAgent │ PythonDaemon (TCP 19002) │
109
+ │ Checkpoint │ ShadowAgent │ Swarm (fan_out/race) │
110
+ │ Evidence │ Supervisor │ │
111
+ │ Storage │ │ │
112
+ └──────────────┴──────────────┴───────────────────────────┘
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Key Innovations
118
+
119
+ ### Jordan Algebraic MoE Routing
120
+
121
+ Replaces softmax gating with Jordan algebra (Spin Factor) composition:
122
+
123
+ - **Non-associative** — Agent grouping topology changes routing output mathematically
124
+ - **Fixed-point convergence** — Jordan squaring converges to idempotents (stable routing attractors)
125
+ - **Spectral decomposition** — Provably unique expert assignment via eigenvalue separation
126
+
127
+ ### Four-Paradigm Continuity
128
+
129
+ Agent state survives daemon restarts, crashes, and hot-reloads:
130
+
131
+ | Paradigm | Mechanism | Speed | Use Case |
132
+ |----------|-----------|-------|----------|
133
+ | Env bitmask | `os.environ` (64-bit) | Fast | Hot restart via `os.execv` |
134
+ | Seed chain | Blake2b derivation (24 bytes) | Fast | Deterministic replay |
135
+ | Inode flags | Zero-byte files (`stat()`) | Kernel | Boolean state gates |
136
+ | Shared memory | `ctypes` (4KB block) | RAM | Cross-process realtime |
137
+
138
+ ### Binary WORM Storage
139
+
140
+ Append-only binary struct format — no JSON, no injection surface:
141
+
142
+ - 152-byte fixed headers (magic + version + type + timestamps + Blake2b hash + Ed25519 sig)
143
+ - Chain verification (each record hashes the previous)
144
+ - Zero text parsing — immune to JSONL injection, newline attacks, XXE
145
+
146
+ ### Inverted AST with Payload Elimination
147
+
148
+ Routing tree is structurally inverted:
149
+ - Structural nodes (routing_weight=1.0) drive all decisions
150
+ - Payload leaves (routing_weight=0.0) cannot propagate upward
151
+ - Blocklist + XXE pattern detection at parse boundary
152
+
153
+ ### Native IPC Multiplexer
154
+
155
+ Memory-mapped shared buffer with O(1) opcode dispatch:
156
+
157
+ - 34 static opcodes (tools) + dynamic assignment from 0x0100
158
+ - 50us polling loop (C core) or Python asyncio fallback
159
+ - Zero serialization — raw struct read/write via mmap
160
+
161
+ ---
162
+
163
+ ## Line Counts
164
+
165
+ | Component | Lines |
166
+ |-----------|-------|
167
+ | Python machine code layer | 9,251 |
168
+ | NASM x86-64 assembly | 7,019 |
169
+ | Routing pipeline | 1,900 |
170
+ | Continuity layer | 1,536 |
171
+ | Tool registration + IPC | 1,935 |
172
+ | Core (storage, evidence) | 570 |
173
+ | Agents (ReAct, Shadow, Supervisor) | 1,500 |
174
+ | Daemon + Retrieval | 1,500 |
175
+ | Sovereign machine wiring | 514 |
176
+ | **Total** | **~25,700** |
177
+
178
+ ---
179
+
180
+ ## Build Constraints
181
+
182
+ Every artifact in this repository satisfies:
183
+
184
+ ```
185
+ ACTIVE(I) => TRUSTED(I) -- Trust axiom
186
+ ENTROPY(I) <= 0.20 -- Shannon bound (nats)
187
+ NAND truth table verified -- Boolean kernel complete
188
+ DAG acyclic -- No routing cycles
189
+ Glyph mapping injective -- No type collisions
190
+ ProofStatus = PROOF_TRUE -- All constraints pass
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Requirements
196
+
197
+ - Python 3.11+
198
+ - No external dependencies (pure stdlib)
199
+ - Optional: NASM for native assembly compilation
200
+ - Optional: C compiler for IPC core dispatcher
201
+
202
+ ---
203
+
204
+ ## License
205
+
206
+ Proprietary. Copyright SnapKitty / SNAPKITTYWEST.
LICENSE.tri ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TRI-LICENSE STRUCTURE
2
+ =====================
3
+
4
+ This project is available under THREE licensing options:
5
+
6
+ 1. Business Source License 1.1 (BSL-1.1)
7
+ - Source-available with commercial restrictions
8
+ - No managed service offerings at enterprise scale
9
+ - Converts to AGPL-3.0 after transition period (Change Date: 2028-08-08)
10
+
11
+ 2. GNU Affero General Public License v3.0 (AGPL-3.0)
12
+ - Strong network copyleft
13
+ - SaaS/network distribution triggers source disclosure
14
+ - All modifications must be AGPL-3.0
15
+
16
+ 3. Mozilla Public License 2.0 (MPL-2.0) + Commercial Dual License
17
+ - Weak copyleft (file-level)
18
+ - Can combine with proprietary code
19
+ - Modified files must remain MPL-2.0
20
+ - Commercial license available for copyleft bypass
21
+
22
+ ================================================================================
23
+
24
+ WHICH LICENSE APPLIES?
25
+
26
+ Use this engine itself to determine:
27
+
28
+ swipl -q -t halt -f license_policy.pl -- select <your_use_case>
29
+
30
+ Use cases:
31
+ - saas_wrapper → AGPL-3.0
32
+ - enterprise_restricted → BSL-1.1
33
+ - file_level_mod → MPL-2.0
34
+ - copyleft_bypass → Commercial
35
+ - open_source_redistribution → AGPL-3.0
36
+
37
+ ================================================================================
38
+
39
+ COPYRIGHT HOLDER
40
+
41
+ Copyright (C) 2026 Ahmad Ali Parr
42
+ Bel Esprit D'Accord Irrevocable Trust
43
+ SnapKitty Collective Limited (FLP)
44
+
45
+ Contact: ahmedparr93@gmail.com
46
+ Web: https://github.com/SNAPKITTYWEST
47
+
48
+ ================================================================================
PRODUCTION_HARDENING.md ADDED
@@ -0,0 +1,2149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PRODUCTION HARDENING PLAN — SOVEREIGN PYTHON LLM ENGINE
2
+
3
+ **Target Audience:** Codex AI assistant (code implementation agent)
4
+ **Purpose:** Specification for hardening the Sovereign engine for production deployment
5
+ **Scope:** 8 major sections, ~200 actionable items
6
+ **Timeline Estimate:** 4-6 weeks at standard velocity
7
+ **Status:** Requirement specification (pre-implementation)
8
+
9
+ ---
10
+
11
+ ## CRITICAL SUCCESS CRITERIA
12
+
13
+ All work must preserve or enhance these invariants:
14
+
15
+ ```
16
+ ✓ ACTIVE(I) ⟹ TRUSTED(I) — No untrusted agent execution
17
+ ✓ ENTROPY(I) ≤ 0.20 nats — Determinism bound
18
+ ✓ WORM append-only + tamper-detect — No log mutation
19
+ ✓ PathJail + SSRFGuard hold all I/O — Zero filesystem/network escape
20
+ ✓ ReAct max_steps enforced — Runaway prevention
21
+ ✓ Tool approval + rate limit gates — Malicious tool calls blocked
22
+ ✓ Continuity 4-paradigm restart cycle — No state loss on crash
23
+ ```
24
+
25
+ ---
26
+
27
+ ## 1. TERMINAL ACCESS IDE
28
+
29
+ ### Goal
30
+ Rich terminal UI (pure Python curses) that displays live agent execution with routing visualization, tool latency, and continuity status.
31
+
32
+ ### 1.1 Core Terminal UI (`src/terminal/ui.py` — ~500 lines)
33
+
34
+ **Deliverables:**
35
+ - Split-pane layout: left = agent output, right = tool/routing activity
36
+ - Live update loop (async, non-blocking)
37
+ - Event queue from agent → UI bridge
38
+
39
+ **Function Signatures:**
40
+
41
+ ```python
42
+ class TerminalUI:
43
+ """Rich terminal interface for Sovereign engine."""
44
+
45
+ def __init__(self, width: int = 200, height: int = 50):
46
+ """Initialize curses screen and panes."""
47
+ pass
48
+
49
+ async def run(self, agent: ReActAgent) -> None:
50
+ """Main loop: poll agent, update panes, handle input."""
51
+ pass
52
+
53
+ def render_left_pane(self, trajectory: AgentTrajectory) -> None:
54
+ """
55
+ Display agent thought/action/observation stream.
56
+
57
+ Format per step:
58
+ [STEP 3]
59
+ 💭 Thought: "Need to search for recent frameworks..."
60
+ ⚙️ Action: tool_call(web_search, {"query": "LLM frameworks 2026"})
61
+ 👁️ Observation: "Found 14 results..."
62
+ ✅ Reflection: "Progress made, continue"
63
+ """
64
+ pass
65
+
66
+ def render_right_pane(
67
+ self,
68
+ routing_state: dict[str, float],
69
+ tool_latencies: dict[str, float],
70
+ continuity_status: dict[str, Any]
71
+ ) -> None:
72
+ """
73
+ Display routing tree, tool performance, continuity flags.
74
+
75
+ Sections:
76
+ ▶ Jordan MoE Routing
77
+ [Expert 0] weight=0.45 ████████░░
78
+ [Expert 1] weight=0.30 ██████░░░░
79
+ ...
80
+ ▶ Tool Performance (last 10 calls)
81
+ web_search avg=42ms max=105ms calls=8
82
+ db_query avg=15ms max=67ms calls=3
83
+ ▶ Continuity Status
84
+ env_bitmask: 0x001f (seed restored)
85
+ shared_mem: OK (4095/4096 bytes)
86
+ worm_ledger: 127 records
87
+ """
88
+ pass
89
+
90
+ def render_status_bar(self, current_step: int, max_steps: int) -> None:
91
+ """Bottom status line: step count, elapsed time, agent state."""
92
+ pass
93
+ ```
94
+
95
+ **Implementation Notes:**
96
+ - Use Python `curses` (stdlib, all platforms)
97
+ - Non-blocking input via `timeout(100)` (100ms refresh)
98
+ - Queue-based communication from agent to UI (thread-safe)
99
+ - WORM ledger tail displayed as a compact scrollable log
100
+
101
+ ---
102
+
103
+ ### 1.2 Reusable Curses Widgets (`src/terminal/widgets.py` — ~250 lines)
104
+
105
+ **Deliverables:**
106
+ - Pane container (split layout)
107
+ - Progress bar / bar chart
108
+ - Text scroller / log viewer
109
+ - Notification banner
110
+
111
+ **Class Signatures:**
112
+
113
+ ```python
114
+ class Panel:
115
+ """Named rectangular region with border and title."""
116
+
117
+ def __init__(self, x: int, y: int, width: int, height: int, title: str = ""):
118
+ pass
119
+
120
+ def write(self, x: int, y: int, text: str, color: int = 7) -> None:
121
+ """Write text at (x, y) relative to panel origin."""
122
+ pass
123
+
124
+ def clear(self) -> None:
125
+ """Erase all content."""
126
+ pass
127
+
128
+ def border(self, style: str = "box") -> None:
129
+ """Draw border (box | rounded | none)."""
130
+ pass
131
+
132
+ def render(self, stdscr) -> None:
133
+ """Write to curses screen."""
134
+ pass
135
+
136
+
137
+ class ProgressBar:
138
+ """Horizontal bar showing percentage (0–100)."""
139
+
140
+ def __init__(self, width: int = 40, filled_char: str = "█", empty_char: str = "░"):
141
+ pass
142
+
143
+ def set_progress(self, value: float) -> None:
144
+ """Update progress (0.0–1.0)."""
145
+ pass
146
+
147
+ def render(self) -> str:
148
+ """Return string representation."""
149
+ pass
150
+
151
+
152
+ class ScrollableLog:
153
+ """Scrollable text buffer (vim-style navigation)."""
154
+
155
+ def __init__(self, max_lines: int = 1000):
156
+ pass
157
+
158
+ def append(self, line: str) -> None:
159
+ """Add line to log."""
160
+ pass
161
+
162
+ def scroll(self, offset: int) -> None:
163
+ """Scroll by offset lines (negative = up)."""
164
+ pass
165
+
166
+ def render(self, height: int) -> list[str]:
167
+ """Return visible lines."""
168
+ pass
169
+ ```
170
+
171
+ ---
172
+
173
+ ### 1.3 Vim-Style Keybindings (`src/terminal/keybinds.py` — ~150 lines)
174
+
175
+ **Deliverables:**
176
+ - Command mode (`:` prefix)
177
+ - Navigation (hjkl, Page Up/Down, gg/G)
178
+ - Pause/resume agent
179
+
180
+ **Function Signatures:**
181
+
182
+ ```python
183
+ class KeybindRegistry:
184
+ """Map terminal input to actions."""
185
+
186
+ def __init__(self):
187
+ self.bindings: dict[str, Callable] = {}
188
+
189
+ def register(self, key: str, action: Callable, mode: str = "normal") -> None:
190
+ """
191
+ Register key binding.
192
+
193
+ Args:
194
+ key: Key code (e.g., "j", "Page_Down", "C-c")
195
+ action: Callable to invoke
196
+ mode: "normal" or "command"
197
+ """
198
+ pass
199
+
200
+ def handle_input(self, ch: int, mode: str = "normal") -> tuple[str, Callable | None]:
201
+ """
202
+ Convert curses input to action.
203
+
204
+ Returns:
205
+ (mode, action_func) or (mode, None) if no binding
206
+ """
207
+ pass
208
+
209
+
210
+ # Predefined bindings:
211
+ BINDINGS = {
212
+ "j": scroll_down,
213
+ "k": scroll_up,
214
+ "Page_Down": scroll_page_down,
215
+ "Page_Up": scroll_page_up,
216
+ "g": scroll_top,
217
+ "G": scroll_bottom,
218
+ "q": quit,
219
+ " ": pause_agent,
220
+ "r": resume_agent,
221
+ "C-c": emergency_stop,
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ ## 2. ACCESSIBILITY
228
+
229
+ ### Goal
230
+ All output (terminal, logs, errors) must support screen readers, high-contrast mode, and dyslexia-friendly rendering. Keyboard-only navigation. No color dependency.
231
+
232
+ ### 2.1 Screen Reader Support (`src/accessibility/screen_reader.py` — ~200 lines)
233
+
234
+ **Deliverables:**
235
+ - Semantic text descriptions for all UI elements
236
+ - Status change announcements
237
+ - Error message plain English
238
+
239
+ **Function Signatures:**
240
+
241
+ ```python
242
+ class AccessibilityBridge:
243
+ """Convert UI events to screen-reader-friendly text."""
244
+
245
+ def __init__(self, output_file: Path | None = None):
246
+ """
247
+ Args:
248
+ output_file: Optional file to log accessibility text.
249
+ If None, print to stdout for screen reader capture.
250
+ """
251
+ pass
252
+
253
+ def announce(self, text: str, priority: str = "normal") -> None:
254
+ """
255
+ Announce text immediately.
256
+
257
+ Args:
258
+ text: Plain English description (no emoji, no color codes)
259
+ priority: "normal" (queue) | "alert" (immediate) | "polite" (deferred)
260
+
261
+ Example:
262
+ announce("Agent entered THINKING state. Step 3 of 10.")
263
+ announce("ERROR: Database query timeout. Retry with longer timeout.", priority="alert")
264
+ """
265
+ pass
266
+
267
+ def format_tool_call(self, tool_name: str, args: dict[str, Any]) -> str:
268
+ """
269
+ Convert tool call to descriptive text.
270
+
271
+ Example output:
272
+ "Called web_search with query: 'LLM frameworks 2026', max_results: 10"
273
+ """
274
+ pass
275
+
276
+ def format_routing_decision(self, experts: dict[str, float]) -> str:
277
+ """
278
+ Describe which experts were activated.
279
+
280
+ Example output:
281
+ "Routing decision: Expert0 weighted 0.45, Expert1 weighted 0.30, others suppressed"
282
+ """
283
+ pass
284
+
285
+ def format_error(self, error: Exception, recovery: str | None = None) -> str:
286
+ """
287
+ Convert exception to plain English with recovery steps.
288
+
289
+ Example:
290
+ format_error(
291
+ PathJailError("Path escapes jail"),
292
+ recovery="Ensure file path is within /workspace"
293
+ )
294
+ # Output: "Error: Cannot access that file path. It is outside the allowed directory.
295
+ # Solution: Ensure file path is within /workspace"
296
+ """
297
+ pass
298
+ ```
299
+
300
+ ---
301
+
302
+ ### 2.2 Contrast & Dyslexia (`src/accessibility/contrast.py` — ~200 lines)
303
+
304
+ **Deliverables:**
305
+ - High-contrast color palette (WCAG AAA compatible)
306
+ - Dyslexia-friendly fonts (monospace, anti-aliased)
307
+ - No ambiguous characters (0/O, 1/l/I, 5/S confusion)
308
+
309
+ **Function Signatures:**
310
+
311
+ ```python
312
+ class ContrastManager:
313
+ """Ensure terminal colors meet accessibility standards."""
314
+
315
+ def __init__(self, mode: str = "normal"):
316
+ """
317
+ Args:
318
+ mode: "normal" | "high_contrast" | "dyslexia_friendly"
319
+ """
320
+ self.mode = mode
321
+ self.palette = self._init_palette()
322
+
323
+ def get_color_pair(self, role: str) -> tuple[int, int]:
324
+ """
325
+ Get foreground, background curses color indices.
326
+
327
+ Roles:
328
+ "text" -> (fg, bg) for normal text
329
+ "thought" -> for agent thinking
330
+ "action" -> for tool calls
331
+ "observation" -> for tool results
332
+ "error" -> for errors
333
+ "success" -> for completed steps
334
+ "warning" -> for warnings
335
+ "status_active" -> for active components
336
+ "status_idle" -> for idle components
337
+
338
+ Returns:
339
+ (fg_color_code, bg_color_code) per curses
340
+ """
341
+ pass
342
+
343
+ def sanitize_output(self, text: str) -> str:
344
+ """
345
+ Remove ambiguous characters, add spacing.
346
+
347
+ Rules:
348
+ - Replace 0/O ambiguity: 0 → O (context-aware) or use distinct rendering
349
+ - Replace 1/l/I: use font distinguishing
350
+ - Increase line spacing in output
351
+ - Ensure monospace rendering
352
+ """
353
+ pass
354
+
355
+ def validate_contrast(self, fg: int, bg: int) -> float:
356
+ """
357
+ Return contrast ratio (1–21, target ≥7 for WCAG AAA).
358
+
359
+ Raises:
360
+ AccessibilityError if ratio < 7
361
+ """
362
+ pass
363
+ ```
364
+
365
+ ---
366
+
367
+ ### 2.3 Keyboard-Only Navigation
368
+
369
+ **Requirements (no implementation, validation only):**
370
+ - All UI features reachable via keyboard
371
+ - Tab order documented
372
+ - Vim keybindings supported (j/k/hjkl)
373
+ - Emergency stop (Ctrl+C) always works
374
+ - No mouse clicks required
375
+
376
+ **Test Cases:**
377
+ ```python
378
+ # tests/accessibility/test_keyboard_nav.py
379
+
380
+ def test_tab_order():
381
+ """Verify Tab cycles through all interactive elements."""
382
+ pass
383
+
384
+ def test_vim_navigation():
385
+ """Verify j/k/hjkl/gg/G work without vim mode enabled."""
386
+ pass
387
+
388
+ def test_no_mouse_required():
389
+ """Verify all features work with keyboard only."""
390
+ pass
391
+ ```
392
+
393
+ ---
394
+
395
+ ## 3. EDGE CASES & ERROR HANDLING
396
+
397
+ ### Goal
398
+ Gracefully handle all failure modes: disk full, corrupt files, path traversal attempts, DNS rebinding, hung tools, queue overflow, model timeouts, C frontend crash.
399
+
400
+ ### 3.1 PathJail Extensions (`src/core/path_jail.py` — extend by ~100 lines)
401
+
402
+ **Add Functions:**
403
+
404
+ ```python
405
+ def detect_junction(path: Path) -> bool:
406
+ """
407
+ Detect Windows NTFS junction points (directory symlinks).
408
+
409
+ On Windows, junctions are not symlinks but still can escape jail.
410
+ Use Windows API reparse point detection.
411
+
412
+ Returns:
413
+ True if path is a junction, False otherwise
414
+ """
415
+ pass
416
+
417
+ def safe_open_nofollow(path: Path, mode: str = 'r') -> io.IOBase:
418
+ """
419
+ Open file with O_NOFOLLOW equivalent (no symlink dereference).
420
+
421
+ On POSIX: Use os.open with os.O_NOFOLLOW
422
+ On Windows: Check is_symlink() before opening
423
+
424
+ Raises:
425
+ PathJailError if file is a symlink
426
+ """
427
+ pass
428
+
429
+ def prevent_toctou_race(path: Path, operation: Callable) -> Any:
430
+ """
431
+ Execute operation on path with TOCTOU race prevention.
432
+
433
+ Pattern: Stat → resolve → stat-again → assert unchanged → execute
434
+
435
+ Args:
436
+ path: Path to operate on
437
+ operation: Callable(path) to execute
438
+
439
+ Returns:
440
+ Result of operation
441
+
442
+ Raises:
443
+ PathJailError if file changed between checks
444
+ """
445
+ pass
446
+ ```
447
+
448
+ **Add to PathJail class:**
449
+
450
+ ```python
451
+ def set_max_file_size(self, max_bytes: int) -> None:
452
+ """
453
+ Enforce max read size per operation.
454
+
455
+ Args:
456
+ max_bytes: Maximum bytes to read (e.g., 1GB)
457
+
458
+ Prevents: DoS by huge file read
459
+ """
460
+ self.max_file_size = max_bytes
461
+
462
+ def read_safe(self, path: str | Path, max_size: int | None = None) -> bytes:
463
+ """
464
+ Read file with size enforcement.
465
+
466
+ Checks available space before read.
467
+
468
+ Raises:
469
+ PathJailError if file exceeds max_size
470
+ """
471
+ pass
472
+ ```
473
+
474
+ ---
475
+
476
+ ### 3.2 SSRFGuard Extensions (`src/core/path_jail.py` — extend SSRFGuard by ~80 lines)
477
+
478
+ **Add Method:**
479
+
480
+ ```python
481
+ class SSRFGuard:
482
+ """Prevent Server-Side Request Forgery."""
483
+
484
+ async def resolve_and_check(self, hostname: str, port: int = 443) -> str:
485
+ """
486
+ DNS rebinding prevention: resolve hostname, check IP, then connect.
487
+
488
+ Algorithm:
489
+ 1. Resolve hostname to IP
490
+ 2. Check IP against blocklist (private ranges, metadata, etc)
491
+ 3. If OK, return IP
492
+ 4. Return IP (not hostname) to caller for actual connection
493
+ 5. On connect, verify IP matches resolved value
494
+
495
+ Args:
496
+ hostname: Domain name
497
+ port: Port (only used for validation, not connection)
498
+
499
+ Returns:
500
+ Verified IP address
501
+
502
+ Raises:
503
+ SSRFError if IP is blocked or doesn't resolve
504
+
505
+ Prevents:
506
+ - DNS rebinding: resolve → check → rebind → connect-to-different-IP
507
+ - Time-of-check-time-of-use: re-resolve before connect
508
+ """
509
+ pass
510
+
511
+ def add_blocked_ip(self, ip: str) -> None:
512
+ """Dynamically add blocked IP."""
513
+ pass
514
+
515
+ def is_blocked(self, ip: str) -> bool:
516
+ """Check if IP is in blocklist."""
517
+ pass
518
+ ```
519
+
520
+ ---
521
+
522
+ ### 3.3 Storage: Disk Full Handling (`src/core/storage.py` — extend by ~60 lines)
523
+
524
+ **Add Functions:**
525
+
526
+ ```python
527
+ def check_disk_space_before_write(
528
+ file_path: Path,
529
+ estimated_size: int
530
+ ) -> bool:
531
+ """
532
+ Pre-check available disk space.
533
+
534
+ Args:
535
+ file_path: Target file path
536
+ estimated_size: Bytes to write
537
+
538
+ Returns:
539
+ True if space available, False if full
540
+
541
+ Raises:
542
+ OSError if path invalid
543
+ """
544
+ pass
545
+
546
+ def write_with_space_check(
547
+ file_path: Path,
548
+ data: bytes,
549
+ min_free_space: int = 1_000_000 # 1MB minimum free
550
+ ) -> None:
551
+ """
552
+ Write to file, abort if disk nearly full.
553
+
554
+ Raises:
555
+ IOError if insufficient space
556
+ """
557
+ pass
558
+
559
+ # Update WORMFile class:
560
+ class WORMFile:
561
+ def append_safe(self, record: WORMRecord) -> None:
562
+ """
563
+ Append record with disk-full protection.
564
+
565
+ - Check space before write
566
+ - Write atomically (temp file + rename)
567
+ - On failure, truncate corrupt tail (not head)
568
+ """
569
+ pass
570
+ ```
571
+
572
+ ---
573
+
574
+ ### 3.4 Continuity: Shared Memory Cleanup (`src/continuity/shared_mem.py` — extend by ~100 lines)
575
+
576
+ **Add Functions:**
577
+
578
+ ```python
579
+ def cleanup_stale_shm_segments(max_age_seconds: int = 3600) -> int:
580
+ """
581
+ Clean up shared memory segments from dead processes.
582
+
583
+ Algorithm:
584
+ 1. List all /dev/shm/* or Windows shared memory
585
+ 2. Check mtime (modification time)
586
+ 3. If older than max_age_seconds AND no process holding it, delete
587
+ 4. Log deletion
588
+
589
+ Args:
590
+ max_age_seconds: Time threshold (default 1 hour)
591
+
592
+ Returns:
593
+ Number of segments cleaned
594
+
595
+ Raises:
596
+ OSError if cannot read /dev/shm
597
+ """
598
+ pass
599
+
600
+ def detect_orphaned_shm(agent_id: str) -> bool:
601
+ """
602
+ Detect if agent has orphaned shared memory from crash.
603
+
604
+ Returns:
605
+ True if orphaned SHM found, False if clean or in use
606
+ """
607
+ pass
608
+
609
+ # Update SharedMemory class:
610
+ class SharedMemory:
611
+ def __init__(self, agent_id: str, size: int = 4096):
612
+ """
613
+ Initialize shared memory with crash-recovery.
614
+
615
+ - Detect orphaned segment from prior crash
616
+ - Zero-out if corrupted
617
+ - Initialize checksum
618
+ """
619
+ pass
620
+
621
+ def validate_checksum(self) -> bool:
622
+ """
623
+ Verify ring buffer header checksum.
624
+
625
+ Returns:
626
+ True if checksum valid, False if corrupted
627
+ """
628
+ pass
629
+
630
+ def recover_from_corruption(self) -> None:
631
+ """
632
+ Reset SHM to safe state if checksum fails.
633
+
634
+ Logs recovery action.
635
+ """
636
+ pass
637
+ ```
638
+
639
+ ---
640
+
641
+ ### 3.5 IPC: Mmap Corruption Detection (`src/tools/ipc_router.py` — extend by ~80 lines)
642
+
643
+ **Add Functions:**
644
+
645
+ ```python
646
+ def validate_ring_buffer_header(mmap_obj: mmap.mmap) -> tuple[bool, str]:
647
+ """
648
+ Verify ring buffer header integrity.
649
+
650
+ Returns:
651
+ (is_valid, error_message)
652
+
653
+ Checks:
654
+ - Magic bytes at offset 0
655
+ - Version byte at offset 4
656
+ - CRC32 at offset 8
657
+ - Write pointer in valid range
658
+ - Read pointer in valid range
659
+ """
660
+ pass
661
+
662
+ def repair_corrupted_ring_buffer(mmap_obj: mmap.mmap) -> None:
663
+ """
664
+ Reset ring buffer to safe state.
665
+
666
+ - Zero-out entire header
667
+ - Reset pointers to 0
668
+ - Recalculate checksum
669
+ - Log repair event
670
+ """
671
+ pass
672
+
673
+ # Update IPCRouter class:
674
+ class IPCRouter:
675
+ async def dispatch_with_corruption_check(self, opcode: int, payload: bytes) -> Any:
676
+ """
677
+ Dispatch opcode, detecting and recovering from mmap corruption.
678
+
679
+ Raises:
680
+ IPCCorruptionError if corruption detected and unrecoverable
681
+ """
682
+ pass
683
+ ```
684
+
685
+ ---
686
+
687
+ ### 3.6 Routing: All-Experts-Masked Fallback (`src/routing/jordan_moe.py` — extend by ~40 lines)
688
+
689
+ **Add Method:**
690
+
691
+ ```python
692
+ class JordanMoERouter:
693
+ def route_with_fallback(self, signal: dict[str, float]) -> dict[str, float]:
694
+ """
695
+ Route signal to experts, with fallback if all masked.
696
+
697
+ Algorithm:
698
+ 1. Compute routing normally
699
+ 2. If all weights = 0 (all masked):
700
+ a. Log warning
701
+ b. Fall back to uniform random distribution
702
+ c. OR use prior distribution (if available)
703
+ d. Return fallback weights
704
+
705
+ Returns:
706
+ Expert → weight mapping (always sums to 1.0)
707
+ """
708
+ pass
709
+
710
+ def get_current_masks(self) -> dict[str, bool]:
711
+ """Return which experts are currently masked."""
712
+ pass
713
+
714
+ def set_emergency_fallback_mode(self, enabled: bool) -> None:
715
+ """
716
+ Enable/disable emergency fallback routing.
717
+
718
+ When enabled and all experts masked, use uniform random.
719
+ When disabled, raise RoutingError.
720
+ """
721
+ pass
722
+ ```
723
+
724
+ ---
725
+
726
+ ### 3.7 Agent: Model Timeout & Backoff (`src/agents/react.py` — extend by ~100 lines)
727
+
728
+ **Add to ReActAgent:**
729
+
730
+ ```python
731
+ class ReActAgent:
732
+ def __init__(self, ..., timeout_config: TimeoutConfig | None = None):
733
+ """
734
+ Args:
735
+ timeout_config: Timeout and retry strategy
736
+ """
737
+ self.timeout_config = timeout_config or TimeoutConfig()
738
+
739
+ async def call_model_with_retry(
740
+ self,
741
+ prompt: str,
742
+ max_retries: int = 3
743
+ ) -> str:
744
+ """
745
+ Call LLM with exponential backoff on timeout/rate-limit.
746
+
747
+ Algorithm:
748
+ - Attempt 1: 30s timeout
749
+ - Attempt 2: 60s timeout, wait 2s before retry
750
+ - Attempt 3: 90s timeout, wait 4s before retry
751
+ - Raise ModelTimeoutError if all attempts fail
752
+
753
+ Detects:
754
+ - Timeout (socket/asyncio timeout)
755
+ - Rate limit (429 HTTP status)
756
+ - Server error (500–599 HTTP)
757
+
758
+ Args:
759
+ prompt: Input to model
760
+ max_retries: Max attempts
761
+
762
+ Returns:
763
+ Model output
764
+
765
+ Raises:
766
+ ModelTimeoutError
767
+ ModelRateLimitError
768
+ """
769
+ pass
770
+ ```
771
+
772
+ **Add TimeoutConfig dataclass:**
773
+
774
+ ```python
775
+ @dataclass
776
+ class TimeoutConfig:
777
+ initial_timeout_sec: float = 30
778
+ max_timeout_sec: float = 120
779
+ backoff_multiplier: float = 2.0
780
+ initial_retry_delay_sec: float = 2
781
+ max_retry_delay_sec: float = 60
782
+ jitter_enabled: bool = True # Add random noise to prevent thundering herd
783
+ ```
784
+
785
+ ---
786
+
787
+ ### 3.8 Shadow: Queue Overflow (`src/agents/shadow.py` — extend by ~60 lines)
788
+
789
+ **Add to ShadowAgent:**
790
+
791
+ ```python
792
+ class ShadowAgent:
793
+ def __init__(self, ..., max_queue_size: int = 10000):
794
+ """
795
+ Args:
796
+ max_queue_size: Max observations in queue before dropping
797
+ """
798
+ self.max_queue_size = max_queue_size
799
+ self.queue_depth_warn_threshold = 0.8 * max_queue_size
800
+
801
+ async def append_observation(self, obs: str) -> None:
802
+ """
803
+ Append observation, dropping oldest if queue full.
804
+
805
+ - If queue at 80% capacity, log warning
806
+ - If queue at 100% capacity, drop oldest, log error
807
+ - Track drop count for metrics
808
+ """
809
+ pass
810
+
811
+ def get_queue_stats(self) -> dict[str, int]:
812
+ """
813
+ Return queue stats.
814
+
815
+ Returns:
816
+ {
817
+ "size": current_size,
818
+ "max": max_size,
819
+ "dropped": total_dropped,
820
+ "utilization_percent": 100 * size / max
821
+ }
822
+ """
823
+ pass
824
+ ```
825
+
826
+ ---
827
+
828
+ ### 3.9 Tools: Per-Tool Timeout (`src/tools/registry.py` — extend by ~50 lines)
829
+
830
+ **Add to ToolDefinition:**
831
+
832
+ ```python
833
+ @dataclass
834
+ class ToolDefinition:
835
+ # ... existing fields ...
836
+
837
+ timeout_sec: float = 60 # Default 60s per tool call
838
+ max_retries: int = 1
839
+ retry_backoff_sec: float = 2.0
840
+ ```
841
+
842
+ **Add function:**
843
+
844
+ ```python
845
+ async def call_tool_with_timeout(
846
+ tool: ToolDefinition,
847
+ args: dict[str, Any],
848
+ logger: logging.Logger
849
+ ) -> Any:
850
+ """
851
+ Call tool with per-tool timeout enforcement.
852
+
853
+ - Set asyncio timeout to tool.timeout_sec
854
+ - If timeout, cancel task and log
855
+ - Raise ToolTimeoutError
856
+
857
+ Raises:
858
+ ToolTimeoutError
859
+ asyncio.TimeoutError (caught and converted)
860
+ """
861
+ pass
862
+ ```
863
+
864
+ ---
865
+
866
+ ### 3.10 Bridge: C Frontend Heartbeat (`src/bridge/http_server.py` — extend by ~80 lines)
867
+
868
+ **Add heartbeat mechanism:**
869
+
870
+ ```python
871
+ class HTTPBridge:
872
+ def __init__(self, ..., heartbeat_interval_sec: float = 5.0):
873
+ """
874
+ Args:
875
+ heartbeat_interval_sec: Interval to send heartbeats to C frontend
876
+ """
877
+ self.heartbeat_interval = heartbeat_interval_sec
878
+ self.last_frontend_ping: float | None = None
879
+
880
+ async def heartbeat_monitor(self) -> None:
881
+ """
882
+ Periodically send heartbeat to frontend.
883
+
884
+ - Send HTTP GET /health every heartbeat_interval seconds
885
+ - If no response for 3x interval, assume frontend dead
886
+ - Clean up resources (close WebSocket, free memory)
887
+ - Log frontend crash
888
+ """
889
+ pass
890
+
891
+ async def handle_frontend_disconnect(self) -> None:
892
+ """
893
+ Called when frontend heartbeat fails.
894
+
895
+ - Cancel any in-progress requests
896
+ - Save agent state (WORM checkpoint)
897
+ - Release mmap/IPC resources
898
+ - Log event
899
+ """
900
+ pass
901
+
902
+ @property
903
+ def frontend_alive(self) -> bool:
904
+ """True if frontend responded to recent heartbeat."""
905
+ pass
906
+ ```
907
+
908
+ ---
909
+
910
+ ## 4. SECURITY HARDENING
911
+
912
+ ### Goal
913
+ Extend PathJail/SSRFGuard, add rate limiting, cost tracking, emergency kill switch, validate inputs, prevent regex DoS.
914
+
915
+ ### 4.1 PathJail & SSRFGuard (See Section 3.1–3.2)
916
+
917
+ ---
918
+
919
+ ### 4.2 Rate Limiting (`src/tools/approval.py` — extend by ~150 lines)
920
+
921
+ **Add class:**
922
+
923
+ ```python
924
+ @dataclass
925
+ class RateLimitPolicy:
926
+ """Per-tool rate limiting configuration."""
927
+ tool_name: str
928
+ max_calls_per_minute: int
929
+ max_calls_per_hour: int
930
+ burst_allowed: int = 5 # Allow burst up to 5 calls
931
+
932
+ @dataclass
933
+ class RateLimitViolation:
934
+ """Result of rate limit check."""
935
+ violated: bool
936
+ calls_made: int
937
+ calls_remaining: int
938
+ reset_time_sec: float
939
+
940
+
941
+ class RateLimiter:
942
+ """Token bucket rate limiter per tool."""
943
+
944
+ def __init__(self, policy: RateLimitPolicy):
945
+ pass
946
+
947
+ def check_rate_limit(self) -> RateLimitViolation:
948
+ """
949
+ Check if tool call allowed.
950
+
951
+ Uses token bucket algorithm:
952
+ - Bucket has capacity = max_calls_per_minute
953
+ - Tokens regenerate at rate = capacity / 60 per second
954
+ - Each call consumes 1 token
955
+ - Burst = allow 5 calls above steady-state
956
+
957
+ Returns:
958
+ RateLimitViolation with status
959
+ """
960
+ pass
961
+
962
+ def get_stats(self) -> dict[str, int]:
963
+ """Return current rate limit stats."""
964
+ pass
965
+
966
+
967
+ # Update ApprovalEngine:
968
+ class ApprovalEngine:
969
+ def __init__(self, ..., rate_limit_policies: dict[str, RateLimitPolicy] | None = None):
970
+ """
971
+ Args:
972
+ rate_limit_policies: Tool name → RateLimitPolicy mapping
973
+ """
974
+ self.rate_limiters = {
975
+ name: RateLimiter(policy)
976
+ for name, policy in (rate_limit_policies or {}).items()
977
+ }
978
+
979
+ async def check_rate_limit(self, tool_name: str) -> RateLimitViolation:
980
+ """Check if tool call violates rate limit."""
981
+ if tool_name not in self.rate_limiters:
982
+ return RateLimitViolation(violated=False, ...)
983
+ return self.rate_limiters[tool_name].check_rate_limit()
984
+
985
+ async def check_approval(self, ...) -> ApprovalResult:
986
+ """Extended to also check rate limit."""
987
+ # Check rate limit first
988
+ rate_check = await self.check_rate_limit(tool.name)
989
+ if rate_check.violated:
990
+ return ApprovalResult(
991
+ approved=False,
992
+ reason=f"Rate limit exceeded: {rate_check.calls_remaining} calls remaining",
993
+ ...
994
+ )
995
+ # Then check existing approval logic
996
+ pass
997
+ ```
998
+
999
+ ---
1000
+
1001
+ ### 4.3 Cost Tracking (`src/tools/approval.py` — extend by ~120 lines)
1002
+
1003
+ **Add class:**
1004
+
1005
+ ```python
1006
+ @dataclass
1007
+ class ToolCost:
1008
+ """Cost of a single tool call."""
1009
+ tool_name: str
1010
+ cost_usd: float
1011
+ timestamp: str
1012
+ actor: str
1013
+ duration_sec: float
1014
+ tokens_used: int | None = None # For LLM calls
1015
+
1016
+
1017
+ class CostTracker:
1018
+ """Track cumulative cost of tool calls."""
1019
+
1020
+ def __init__(self, ledger: WORMLedger):
1021
+ self.ledger = ledger
1022
+
1023
+ def log_cost(self, cost: ToolCost) -> None:
1024
+ """
1025
+ Log tool call cost to WORM.
1026
+
1027
+ Records:
1028
+ - tool_name
1029
+ - cost_usd (e.g., 0.0012)
1030
+ - duration
1031
+ - tokens (if applicable)
1032
+ - timestamp
1033
+ """
1034
+ pass
1035
+
1036
+ async def get_total_cost(self, actor: str | None = None, since: datetime | None = None) -> float:
1037
+ """
1038
+ Compute total cost incurred.
1039
+
1040
+ Args:
1041
+ actor: Filter by actor (None = all)
1042
+ since: Filter by time (None = all time)
1043
+
1044
+ Returns:
1045
+ Total cost in USD
1046
+ """
1047
+ pass
1048
+
1049
+ async def get_cost_by_tool(self) -> dict[str, float]:
1050
+ """Break down cost by tool."""
1051
+ pass
1052
+
1053
+ def set_monthly_budget_usd(self, budget: float) -> None:
1054
+ """Set max monthly spending."""
1055
+ pass
1056
+
1057
+ async def check_budget(self, actor: str) -> tuple[bool, str]:
1058
+ """
1059
+ Check if remaining budget allows next tool call.
1060
+
1061
+ Returns:
1062
+ (budget_ok, reason)
1063
+ """
1064
+ pass
1065
+
1066
+
1067
+ # Update ApprovalEngine:
1068
+ class ApprovalEngine:
1069
+ def __init__(self, ..., cost_tracker: CostTracker | None = None):
1070
+ self.cost_tracker = cost_tracker
1071
+
1072
+ async def check_approval(self, tool: ToolDefinition, ...) -> ApprovalResult:
1073
+ """Extended to check cost budget."""
1074
+ if self.cost_tracker:
1075
+ budget_ok, reason = await self.cost_tracker.check_budget(actor)
1076
+ if not budget_ok:
1077
+ return ApprovalResult(approved=False, reason=reason, ...)
1078
+ # Continue with existing checks
1079
+ pass
1080
+ ```
1081
+
1082
+ ---
1083
+
1084
+ ### 4.4 Emergency Kill Switch (`src/tools/supervisor.py` — extend by ~80 lines)
1085
+
1086
+ **Add class:**
1087
+
1088
+ ```python
1089
+ class EmergencyKillSwitch:
1090
+ """
1091
+ Emergency stop: disable all tools immediately.
1092
+ """
1093
+
1094
+ def __init__(self, approval_engine: ApprovalEngine):
1095
+ self.approval_engine = approval_engine
1096
+ self._kill_switch_engaged = False
1097
+
1098
+ def engage(self, reason: str, actor: str) -> None:
1099
+ """
1100
+ Engage kill switch: disable all tool execution.
1101
+
1102
+ Args:
1103
+ reason: Why kill switch engaged (logged)
1104
+ actor: Who engaged it (logged)
1105
+
1106
+ Effect:
1107
+ - All subsequent tool calls return ApprovalResult(approved=False)
1108
+ - WORM record created
1109
+ - Alert sent to admin
1110
+ """
1111
+ pass
1112
+
1113
+ def disengage(self, reason: str, actor: str) -> None:
1114
+ """Disable kill switch (requires auth)."""
1115
+ pass
1116
+
1117
+ def is_engaged(self) -> bool:
1118
+ """Check current state."""
1119
+ pass
1120
+
1121
+ def force_stop_all_agents(self) -> int:
1122
+ """
1123
+ Stop all running agents immediately.
1124
+
1125
+ Returns:
1126
+ Number of agents stopped
1127
+ """
1128
+ pass
1129
+
1130
+
1131
+ # Add to main engine init:
1132
+ def __init__(self, ...):
1133
+ self.kill_switch = EmergencyKillSwitch(self.approval_engine)
1134
+ ```
1135
+
1136
+ ---
1137
+
1138
+ ### 4.5 Input Validation (`src/core/types.py` — new file, ~150 lines)
1139
+
1140
+ **Add validators:**
1141
+
1142
+ ```python
1143
+ class InputValidator:
1144
+ """Validate all untrusted input."""
1145
+
1146
+ MAX_MESSAGE_LENGTH = 100_000 # 100KB
1147
+ MAX_QUERY_LENGTH = 10_000 # 10KB
1148
+ REGEX_TIMEOUT_MS = 100
1149
+
1150
+ @staticmethod
1151
+ def validate_message_length(msg: str) -> None:
1152
+ """Enforce max message length."""
1153
+ if len(msg) > InputValidator.MAX_MESSAGE_LENGTH:
1154
+ raise ValueError(
1155
+ f"Message exceeds max length: {len(msg)} > {InputValidator.MAX_MESSAGE_LENGTH}"
1156
+ )
1157
+
1158
+ @staticmethod
1159
+ def validate_unicode_normalization(text: str) -> str:
1160
+ """
1161
+ Normalize Unicode to prevent homograph attacks.
1162
+
1163
+ - Decompose combining characters
1164
+ - Detect confusable scripts (Latin + Cyrillic)
1165
+ - Raise error if mixing detected
1166
+
1167
+ Returns:
1168
+ Normalized text
1169
+ """
1170
+ import unicodedata
1171
+ nfc = unicodedata.normalize('NFC', text)
1172
+ # TODO: Homograph detection
1173
+ return nfc
1174
+
1175
+ @staticmethod
1176
+ def validate_regex_safe(pattern: str) -> bool:
1177
+ """
1178
+ Check regex for potential DoS (catastrophic backtracking).
1179
+
1180
+ Algorithm:
1181
+ - Use timeout-based check: compile pattern with timeout
1182
+ - Test against sample inputs
1183
+ - Raise error if timeout
1184
+
1185
+ Or: Require patterns to match RE2 spec (no backtracking).
1186
+
1187
+ Returns:
1188
+ True if safe
1189
+
1190
+ Raises:
1191
+ RegexDoSError if pattern dangerous
1192
+ """
1193
+ pass
1194
+
1195
+ @staticmethod
1196
+ def validate_json_safe(json_str: str) -> Any:
1197
+ """
1198
+ Parse JSON with size limits.
1199
+
1200
+ - Enforce max nesting depth (10)
1201
+ - Enforce max string length (1MB)
1202
+ - Reject duplicate keys (potential collision attack)
1203
+ """
1204
+ pass
1205
+
1206
+ @staticmethod
1207
+ def sanitize_sql_parameter(param: str) -> str:
1208
+ """
1209
+ Sanitize SQL parameter (for parameterized queries).
1210
+
1211
+ Note: Should use parameterized queries, not escaping!
1212
+ This is fallback only.
1213
+ """
1214
+ pass
1215
+ ```
1216
+
1217
+ ---
1218
+
1219
+ ### 4.6 Crypto Verification (`src/core/crypto.py` — extend by ~100 lines)
1220
+
1221
+ **Verify existing functionality and extend:**
1222
+
1223
+ ```python
1224
+ class CryptoManager:
1225
+ """Manage cryptographic keys and operations."""
1226
+
1227
+ def __init__(self, master_secret: bytes | None = None):
1228
+ """
1229
+ Args:
1230
+ master_secret: 32-byte master secret (or load from secure store)
1231
+ """
1232
+ self.master_secret = master_secret
1233
+
1234
+ def derive_key(self, context: str, length: int = 32) -> bytes:
1235
+ """
1236
+ Derive key from master secret using HKDF.
1237
+
1238
+ Args:
1239
+ context: String context (e.g., "worm_signing", "aes_encryption")
1240
+ length: Byte length of derived key
1241
+
1242
+ Returns:
1243
+ Derived key
1244
+
1245
+ Algorithm:
1246
+ HKDF-SHA256(master_secret, context.encode(), length)
1247
+ """
1248
+ pass
1249
+
1250
+ def rotate_key(self, old_key_id: str) -> str:
1251
+ """
1252
+ Rotate signing key: generate new key, mark old as retired.
1253
+
1254
+ Args:
1255
+ old_key_id: ID of key to retire
1256
+
1257
+ Returns:
1258
+ ID of new key
1259
+
1260
+ Records rotation in WORM for audit trail.
1261
+ """
1262
+ pass
1263
+
1264
+ def verify_signature_on_read(self, data: bytes, signature: bytes, key_id: str) -> bool:
1265
+ """
1266
+ Verify Ed25519 signature on data read from storage.
1267
+
1268
+ Args:
1269
+ data: Data to verify
1270
+ signature: Signature bytes
1271
+ key_id: ID of key to use for verification
1272
+
1273
+ Returns:
1274
+ True if signature valid
1275
+
1276
+ Raises:
1277
+ CryptoError if key_id not found
1278
+ """
1279
+ pass
1280
+
1281
+ def get_key_metadata(self, key_id: str) -> dict[str, Any]:
1282
+ """Get metadata about a key (created_at, retired_at, etc)."""
1283
+ pass
1284
+ ```
1285
+
1286
+ ---
1287
+
1288
+ ## 5. TESTING
1289
+
1290
+ ### Goal
1291
+ Comprehensive pytest suite (~2000 lines) covering unit, integration, and property-based tests.
1292
+
1293
+ ### 5.1 Unit Tests
1294
+
1295
+ #### `tests/test_routing.py` (~400 lines)
1296
+
1297
+ ```python
1298
+ """Test Jordan algebra MoE routing properties."""
1299
+
1300
+ import pytest
1301
+ from src.routing.jordan_moe import SpinFactor, JordanMoERouter
1302
+
1303
+ class TestSpinFactorAlgebra:
1304
+ """Verify Jordan algebra axioms."""
1305
+
1306
+ def test_commutativity(self):
1307
+ """A ∘ B = B ∘ A"""
1308
+ a = SpinFactor(2.0, [1.0, 0.0])
1309
+ b = SpinFactor(3.0, [0.0, 1.0])
1310
+ assert a.compose(b) == b.compose(a)
1311
+
1312
+ def test_non_associativity(self):
1313
+ """(A ∘ B) ∘ C ≠ A ∘ (B ∘ C) in general"""
1314
+ a = SpinFactor(1.0, [1.0, 0.0])
1315
+ b = SpinFactor(1.0, [0.0, 1.0])
1316
+ c = SpinFactor(1.0, [1.0, 1.0])
1317
+
1318
+ left = a.compose(b).compose(c)
1319
+ right = a.compose(b.compose(c))
1320
+ # Should differ (non-associative)
1321
+ assert left != right
1322
+
1323
+ def test_idempotent_convergence(self):
1324
+ """x ∘ x ∘ x... converges to idempotent e where e ∘ e = e"""
1325
+ x = SpinFactor(0.5, [0.2, 0.3])
1326
+
1327
+ for _ in range(100):
1328
+ x_new = x.compose(x)
1329
+ # Eventually x ≈ e and e ∘ e ≈ e
1330
+ if abs(x_new.scalar - x.scalar) < 1e-6:
1331
+ # Converged, verify idempotency
1332
+ e_squared = x_new.compose(x_new)
1333
+ assert abs(e_squared.scalar - x_new.scalar) < 1e-6
1334
+ break
1335
+ x = x_new
1336
+ ```
1337
+
1338
+ #### `tests/test_storage.py` (~300 lines)
1339
+
1340
+ ```python
1341
+ """Test WORM append-only and tamper detection."""
1342
+
1343
+ import pytest
1344
+ from src.core.storage import WORMFile, WORMRecord
1345
+ from pathlib import Path
1346
+
1347
+ class TestWORMAppendOnly:
1348
+ """WORM records cannot be modified."""
1349
+
1350
+ def test_append_writes_record(self, tmp_path):
1351
+ """Appending writes record correctly."""
1352
+ worm = WORMFile(tmp_path / "test.worm")
1353
+ rec = WORMRecord(event_type="test", payload=b"hello")
1354
+
1355
+ worm.append(rec)
1356
+
1357
+ records = list(worm.read())
1358
+ assert len(records) == 1
1359
+ assert records[0].payload == b"hello"
1360
+
1361
+ def test_chain_verification(self, tmp_path):
1362
+ """Each record hashes the previous."""
1363
+ worm = WORMFile(tmp_path / "test.worm")
1364
+ rec1 = WORMRecord(event_type="evt1", payload=b"data1")
1365
+ rec2 = WORMRecord(event_type="evt2", payload=b"data2")
1366
+
1367
+ worm.append(rec1)
1368
+ worm.append(rec2)
1369
+
1370
+ records = list(worm.read())
1371
+ # rec2.prev_hash should match rec1's content hash
1372
+ assert records[1].prev_hash == records[0].content_hash
1373
+
1374
+ def test_tamper_detection(self, tmp_path):
1375
+ """Corrupt file fails verification."""
1376
+ worm = WORMFile(tmp_path / "test.worm")
1377
+ rec = WORMRecord(event_type="test", payload=b"data")
1378
+ worm.append(rec)
1379
+
1380
+ # Corrupt payload byte
1381
+ with open(worm.path, 'r+b') as f:
1382
+ f.seek(152 + 10) # Skip header
1383
+ f.write(b'X')
1384
+
1385
+ # Reading should detect corruption
1386
+ with pytest.raises(Exception): # WORM corruption error
1387
+ list(worm.read())
1388
+ ```
1389
+
1390
+ #### `tests/test_continuity.py` (~250 lines)
1391
+
1392
+ ```python
1393
+ """Test 4-paradigm state save/restore/restart."""
1394
+
1395
+ import pytest
1396
+ import os
1397
+ from src.continuity.manager import ContinuityManager
1398
+
1399
+ class TestContinuityRestart:
1400
+ """Agent state survives restart."""
1401
+
1402
+ def test_env_state_preserved(self, tmp_path, monkeypatch):
1403
+ """Environment bitmask restored on hot restart."""
1404
+ base_dir = tmp_path / "continuity"
1405
+
1406
+ # First session
1407
+ mgr1 = ContinuityManager(base_dir=base_dir, agent_id="agent1")
1408
+ mgr1.set_env_bitmask(0x001f)
1409
+ mgr1.checkpoint()
1410
+
1411
+ # Simulate restart
1412
+ monkeypatch.delenv("SOVEREIGN_STEP", raising=False)
1413
+
1414
+ # Second session
1415
+ mgr2 = ContinuityManager(base_dir=base_dir, agent_id="agent1")
1416
+ assert mgr2.was_restarted
1417
+ assert mgr2.get_env_bitmask() == 0x001f
1418
+
1419
+ def test_seed_chain_deterministic_replay(self, tmp_path):
1420
+ """Seed-derived randomness is deterministic."""
1421
+ base_dir = tmp_path / "continuity"
1422
+
1423
+ # Session 1: record sequence
1424
+ mgr1 = ContinuityManager(base_dir=base_dir, agent_id="agent1")
1425
+ mgr1.set_seed_chain(b'test_seed')
1426
+ values1 = [mgr1.derive_random() for _ in range(5)]
1427
+
1428
+ # Session 2: replay
1429
+ mgr2 = ContinuityManager(base_dir=base_dir, agent_id="agent1")
1430
+ mgr2.set_seed_chain(b'test_seed')
1431
+ values2 = [mgr2.derive_random() for _ in range(5)]
1432
+
1433
+ assert values1 == values2
1434
+ ```
1435
+
1436
+ #### `tests/test_path_jail.py` (~250 lines)
1437
+
1438
+ ```python
1439
+ """Test PathJail directory traversal prevention."""
1440
+
1441
+ import pytest
1442
+ from src.core.path_jail import PathJail, PathJailError
1443
+
1444
+ class TestPathJail:
1445
+ """Verify all escapes blocked."""
1446
+
1447
+ def test_relative_traversal_blocked(self, tmp_path):
1448
+ """../.. escapes rejected."""
1449
+ jail = PathJail([tmp_path])
1450
+
1451
+ with pytest.raises(PathJailError, match="escapes jail"):
1452
+ jail.resolve(tmp_path / "subdir" / ".." / ".." / "etc" / "passwd")
1453
+
1454
+ def test_symlink_escape_blocked(self, tmp_path):
1455
+ """Symlink outside root rejected."""
1456
+ jail = PathJail([tmp_path])
1457
+ (tmp_path / "etc").mkdir()
1458
+ (tmp_path / "workspace").mkdir()
1459
+
1460
+ # Create symlink inside jail pointing outside
1461
+ import os
1462
+ os.symlink("/etc/passwd", tmp_path / "workspace" / "link")
1463
+
1464
+ with pytest.raises(PathJailError, match="escapes jail"):
1465
+ jail.resolve(tmp_path / "workspace" / "link")
1466
+
1467
+ def test_null_byte_injection_blocked(self, tmp_path):
1468
+ """Null byte in path rejected."""
1469
+ jail = PathJail([tmp_path])
1470
+
1471
+ with pytest.raises(PathJailError, match="Null byte"):
1472
+ jail.resolve(str(tmp_path) + "\x00/etc/passwd")
1473
+
1474
+ def test_safe_path_allowed(self, tmp_path):
1475
+ """Valid path inside root allowed."""
1476
+ jail = PathJail([tmp_path])
1477
+ (tmp_path / "safe.txt").touch()
1478
+
1479
+ result = jail.resolve(tmp_path / "safe.txt")
1480
+ assert result.exists()
1481
+ ```
1482
+
1483
+ #### `tests/test_ipc.py` (~200 lines)
1484
+
1485
+ ```python
1486
+ """Test IPC ring buffer and opcode dispatch."""
1487
+
1488
+ import pytest
1489
+ from src.tools.ipc_router import IPCRouter
1490
+
1491
+ class TestIPCDispatch:
1492
+ """Verify opcode routing and concurrency."""
1493
+
1494
+ async def test_opcode_dispatch(self):
1495
+ """Opcode routes to correct handler."""
1496
+ router = IPCRouter()
1497
+ handler_called = False
1498
+
1499
+ async def mock_handler(payload):
1500
+ nonlocal handler_called
1501
+ handler_called = True
1502
+ return b"result"
1503
+
1504
+ router.register_opcode(0x10, mock_handler)
1505
+ result = await router.dispatch(0x10, b"test")
1506
+
1507
+ assert handler_called
1508
+ assert result == b"result"
1509
+
1510
+ def test_ring_buffer_overflow(self):
1511
+ """Ring buffer wraps at boundary."""
1512
+ router = IPCRouter()
1513
+ # Write beyond buffer size
1514
+ # Verify wrap-around works correctly
1515
+ pass
1516
+
1517
+ async def test_concurrent_dispatch(self):
1518
+ """Multiple concurrent dispatch calls work."""
1519
+ import asyncio
1520
+ router = IPCRouter()
1521
+
1522
+ async def handler(payload):
1523
+ await asyncio.sleep(0.01)
1524
+ return payload
1525
+
1526
+ router.register_opcode(0x20, handler)
1527
+
1528
+ # Launch 10 concurrent calls
1529
+ tasks = [router.dispatch(0x20, f"msg{i}".encode()) for i in range(10)]
1530
+ results = await asyncio.gather(*tasks)
1531
+
1532
+ assert len(results) == 10
1533
+ ```
1534
+
1535
+ #### `tests/test_agent.py` (~200 lines)
1536
+
1537
+ ```python
1538
+ """Test ReAct agent loop and continuity."""
1539
+
1540
+ import pytest
1541
+ from src.agents.react import ReActAgent, ReActConfig
1542
+
1543
+ class TestReActLoop:
1544
+ """Verify agent reasoning loop."""
1545
+
1546
+ async def test_thought_action_observation_cycle(self, mock_model, mock_tool_registry):
1547
+ """Agent loops through thought → action → observation."""
1548
+ agent = ReActAgent(mock_model, mock_tool_registry)
1549
+ trajectory = await agent.run(task="test task")
1550
+
1551
+ assert len(trajectory.steps) > 0
1552
+ assert any(step.action_type == "tool_call" for step in trajectory.steps)
1553
+
1554
+ async def test_max_steps_enforced(self, mock_model, mock_tool_registry):
1555
+ """Agent stops at max_steps."""
1556
+ config = ReActConfig(max_steps=3)
1557
+ agent = ReActAgent(mock_model, mock_tool_registry, config=config)
1558
+
1559
+ trajectory = await agent.run(task="test task")
1560
+
1561
+ assert len(trajectory.steps) <= 3
1562
+
1563
+ async def test_error_reflection(self, mock_model, mock_tool_registry):
1564
+ """Agent reflects on errors."""
1565
+ # Configure mock to return error on first tool call
1566
+ mock_tool_registry.get_tool("test").side_effect = Exception("Tool error")
1567
+
1568
+ config = ReActConfig(reflection_on_error=True)
1569
+ agent = ReActAgent(mock_model, mock_tool_registry, config=config)
1570
+
1571
+ trajectory = await agent.run(task="test")
1572
+
1573
+ # Should include reflection step after error
1574
+ assert any("reflection" in str(step).lower() for step in trajectory.steps)
1575
+ ```
1576
+
1577
+ ### 5.2 Integration Tests
1578
+
1579
+ #### `tests/integration/test_full_pipeline.py` (~250 lines)
1580
+
1581
+ ```python
1582
+ """End-to-end task execution."""
1583
+
1584
+ import pytest
1585
+
1586
+ class TestFullPipeline:
1587
+ """Task → agent → tools → result."""
1588
+
1589
+ async def test_simple_task_execution(self, engine_fixture):
1590
+ """Execute simple task end-to-end."""
1591
+ task = "Search for 'LLM frameworks' and summarize top 3 results"
1592
+
1593
+ result = await engine_fixture.run_task(task)
1594
+
1595
+ assert result is not None
1596
+ assert "framework" in result.lower() or "llm" in result.lower()
1597
+
1598
+ async def test_tool_chain_execution(self, engine_fixture):
1599
+ """Execute multi-tool task."""
1600
+ task = "Download paper from arXiv, summarize key findings"
1601
+
1602
+ result = await engine_fixture.run_task(task)
1603
+
1604
+ # Verify multiple tools were used
1605
+ assert engine_fixture.tool_call_count > 1
1606
+
1607
+ async def test_failure_recovery(self, engine_fixture):
1608
+ """Agent handles tool failure gracefully."""
1609
+ # Simulate tool failure
1610
+ engine_fixture.tools["web_search"].fail_next_call()
1611
+
1612
+ task = "Search for something"
1613
+ result = await engine_fixture.run_task(task)
1614
+
1615
+ # Should succeed despite failure (agent reflects and recovers)
1616
+ assert result is not None
1617
+ ```
1618
+
1619
+ #### `tests/integration/test_bridge.py` (~200 lines)
1620
+
1621
+ ```python
1622
+ """Test HTTP bridge request/response cycle."""
1623
+
1624
+ import pytest
1625
+ from src.bridge.http_server import HTTPBridge
1626
+
1627
+ class TestHTTPBridge:
1628
+ """Bridge between C frontend and Python engine."""
1629
+
1630
+ async def test_request_response_cycle(self, bridge_fixture):
1631
+ """Send request, receive response."""
1632
+ request = {
1633
+ "method": "run_task",
1634
+ "params": {"task": "test"}
1635
+ }
1636
+
1637
+ response = await bridge_fixture.send(request)
1638
+
1639
+ assert "result" in response or "error" in response
1640
+
1641
+ async def test_heartbeat_keeps_bridge_alive(self, bridge_fixture):
1642
+ """Heartbeat prevents timeout."""
1643
+ # Simulate frontend not sending requests
1644
+ import asyncio
1645
+ await asyncio.sleep(10)
1646
+
1647
+ # Bridge should still be alive
1648
+ assert bridge_fixture.is_alive()
1649
+
1650
+ async def test_frontend_crash_cleanup(self, bridge_fixture):
1651
+ """Frontend crash triggers cleanup."""
1652
+ # Simulate frontend crash
1653
+ bridge_fixture.frontend_connection.close()
1654
+
1655
+ # Wait for heartbeat to detect
1656
+ await asyncio.sleep(bridge_fixture.heartbeat_interval * 3)
1657
+
1658
+ # Cleanup should have occurred
1659
+ assert bridge_fixture.agent_state_saved()
1660
+ ```
1661
+
1662
+ ### 5.3 Property-Based Tests (Hypothesis)
1663
+
1664
+ #### `tests/property/test_jordan.py` (~150 lines)
1665
+
1666
+ ```python
1667
+ """Property-based testing of Jordan algebra."""
1668
+
1669
+ from hypothesis import given, strategies as st
1670
+ import pytest
1671
+
1672
+ class TestJordanProperties:
1673
+ """Verify Jordan axioms hold for random inputs."""
1674
+
1675
+ @given(
1676
+ scalar1=st.floats(min_value=-1e6, max_value=1e6),
1677
+ vector1=st.lists(st.floats(min_value=-100, max_value=100), min_size=3, max_size=3),
1678
+ scalar2=st.floats(min_value=-1e6, max_value=1e6),
1679
+ vector2=st.lists(st.floats(min_value=-100, max_value=100), min_size=3, max_size=3),
1680
+ )
1681
+ def test_commutativity_holds(self, scalar1, vector1, scalar2, vector2):
1682
+ """A ∘ B = B ∘ A for all A, B"""
1683
+ from src.routing.jordan_moe import SpinFactor
1684
+
1685
+ a = SpinFactor(scalar1, vector1)
1686
+ b = SpinFactor(scalar2, vector2)
1687
+
1688
+ assert a.compose(b) == b.compose(a)
1689
+
1690
+ @given(
1691
+ scalars=st.lists(st.floats(min_value=-10, max_value=10), min_size=3, max_size=5)
1692
+ )
1693
+ def test_convergence_to_idempotent(self, scalars):
1694
+ """Repeated squaring converges to idempotent."""
1695
+ # TODO: Implement
1696
+ pass
1697
+ ```
1698
+
1699
+ #### `tests/property/test_worm.py` (~150 lines)
1700
+
1701
+ ```python
1702
+ """Property-based testing of WORM append-only."""
1703
+
1704
+ from hypothesis import given, strategies as st
1705
+
1706
+ class TestWORMInvariants:
1707
+ """WORM append-only invariant never violated."""
1708
+
1709
+ @given(
1710
+ payloads=st.lists(st.binary(min_size=1, max_size=1000), min_size=1, max_size=100)
1711
+ )
1712
+ def test_append_only_never_loses_data(self, payloads, tmp_path):
1713
+ """Every appended record remains readable."""
1714
+ from src.core.storage import WORMFile, WORMRecord
1715
+
1716
+ worm = WORMFile(tmp_path / "test.worm")
1717
+
1718
+ for i, payload in enumerate(payloads):
1719
+ rec = WORMRecord(event_type=f"event_{i}", payload=payload)
1720
+ worm.append(rec)
1721
+
1722
+ # Read all and verify
1723
+ records = list(worm.read())
1724
+
1725
+ assert len(records) == len(payloads)
1726
+ for i, rec in enumerate(records):
1727
+ assert rec.payload == payloads[i]
1728
+ ```
1729
+
1730
+ ---
1731
+
1732
+ ## 6. PERFORMANCE OPTIMIZATION
1733
+
1734
+ ### Goal
1735
+ Profile hot paths, optimize to targets, lazy-load modules, cache results, use memory-mapping for large files.
1736
+
1737
+ ### 6.1 Routing Latency (`src/routing/jordan_moe.py` — profile + optimize)
1738
+
1739
+ **Target:** <10ms for 8 experts
1740
+
1741
+ ```python
1742
+ # Add to JordanMoERouter:
1743
+
1744
+ def profile_routing_latency(self, iterations: int = 1000) -> dict[str, float]:
1745
+ """
1746
+ Profile routing latency.
1747
+
1748
+ Returns:
1749
+ {
1750
+ "mean_ms": average latency,
1751
+ "p50_ms": 50th percentile,
1752
+ "p99_ms": 99th percentile,
1753
+ "max_ms": maximum,
1754
+ }
1755
+ """
1756
+ pass
1757
+
1758
+ # Optimization: Cache fixed-point results
1759
+ class JordanMoERouterCached:
1760
+ def __init__(self):
1761
+ self.cache = {} # signal_hash -> weights
1762
+
1763
+ def route_cached(self, signal: dict[str, float]) -> dict[str, float]:
1764
+ """Route with memoization."""
1765
+ signal_hash = hash(frozenset(signal.items()))
1766
+
1767
+ if signal_hash in self.cache:
1768
+ return self.cache[signal_hash]
1769
+
1770
+ result = self.route(signal)
1771
+ self.cache[signal_hash] = result
1772
+ return result
1773
+ ```
1774
+
1775
+ ---
1776
+
1777
+ ### 6.2 IPC Round-Trip (`src/tools/ipc_router.py` — profile + optimize)
1778
+
1779
+ **Target:** <100μs mmap, <1ms HTTP fallback
1780
+
1781
+ ```python
1782
+ async def profile_ipc_latency(router: IPCRouter) -> dict[str, float]:
1783
+ """
1784
+ Profile IPC round-trip latency.
1785
+
1786
+ Returns:
1787
+ {
1788
+ "mmap_mean_us": microseconds,
1789
+ "mmap_p99_us": 99th percentile,
1790
+ "http_mean_ms": milliseconds,
1791
+ }
1792
+ """
1793
+ pass
1794
+ ```
1795
+
1796
+ ---
1797
+
1798
+ ### 6.3 Connection Pooling (`src/runtime/providers/*.py`)
1799
+
1800
+ **Add to all providers:**
1801
+
1802
+ ```python
1803
+ class ModelProvider:
1804
+ def __init__(self, pool_size: int = 10):
1805
+ self.pool = asyncio.Queue(maxsize=pool_size)
1806
+ # Pre-populate with connections
1807
+ for _ in range(pool_size):
1808
+ self.pool.put_nowait(self._create_connection())
1809
+
1810
+ async def get_connection(self):
1811
+ """Get connection from pool."""
1812
+ return await self.pool.get()
1813
+
1814
+ async def release_connection(self, conn):
1815
+ """Return connection to pool."""
1816
+ await self.pool.put(conn)
1817
+ ```
1818
+
1819
+ ---
1820
+
1821
+ ### 6.4 Lazy Module Loading (`src/tools/loader.py` — new/extend)
1822
+
1823
+ ```python
1824
+ class ToolLoader:
1825
+ """Lazy-load tools on demand."""
1826
+
1827
+ def __init__(self):
1828
+ self.loaded_tools = {} # tool_name -> module
1829
+ self.loading_lock = asyncio.Lock()
1830
+
1831
+ async def load_tool(self, tool_name: str):
1832
+ """Load tool module on first use."""
1833
+ async with self.loading_lock:
1834
+ if tool_name in self.loaded_tools:
1835
+ return self.loaded_tools[tool_name]
1836
+
1837
+ module = __import__(f"src.tools.{tool_name}")
1838
+ self.loaded_tools[tool_name] = module
1839
+ return module
1840
+ ```
1841
+
1842
+ ---
1843
+
1844
+ ### 6.5 Binary IR Memory-Mapping (`src/runtime/machine/binary_ir.py` — extend)
1845
+
1846
+ ```python
1847
+ class BinaryIRExecutor:
1848
+ def execute_large_file_with_mmap(self, file_path: Path) -> Any:
1849
+ """
1850
+ Load large binary files via mmap instead of full read.
1851
+
1852
+ Benefit: O(1) memory regardless of file size.
1853
+ """
1854
+ import mmap
1855
+
1856
+ with open(file_path, 'rb') as f:
1857
+ with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
1858
+ # Execute against mmap view
1859
+ return self.execute_ir(mm)
1860
+ ```
1861
+
1862
+ ---
1863
+
1864
+ ## 7. DOCUMENTATION
1865
+
1866
+ ### Goal
1867
+ Complete docstrings, architecture diagrams, API reference, deployment guide, configuration reference.
1868
+
1869
+ ### 7.1 Docstring Convention
1870
+
1871
+ **All public methods must include:**
1872
+
1873
+ ```python
1874
+ def my_function(param1: str, param2: int) -> dict[str, Any]:
1875
+ """
1876
+ One-line summary of what this does.
1877
+
1878
+ Longer description if needed. Mention any important side effects.
1879
+
1880
+ Args:
1881
+ param1: Description of param1
1882
+ param2: Description of param2 (default: X)
1883
+
1884
+ Returns:
1885
+ Description of return value
1886
+
1887
+ Raises:
1888
+ MyError: When condition occurs
1889
+ OtherError: When other condition occurs
1890
+
1891
+ Example:
1892
+ >>> result = my_function("test", 42)
1893
+ >>> print(result)
1894
+ {...}
1895
+ """
1896
+ pass
1897
+ ```
1898
+
1899
+ ### 7.2 Architecture Diagram (in README)
1900
+
1901
+ Mermaid diagram showing:
1902
+ - Routing pipeline (input → JordanMoE → expert selection)
1903
+ - Continuity layer (4 paradigms, checkpoint/restore)
1904
+ - Tool dispatch (opcode registry → IPC)
1905
+ - Agent loop (ReAct + Shadow)
1906
+ - Bridge (HTTP server + C frontend)
1907
+
1908
+ ---
1909
+
1910
+ ### 7.3 API Reference
1911
+
1912
+ Auto-generated from docstrings:
1913
+
1914
+ ```
1915
+ src/routing/
1916
+ - jordan_moe.py
1917
+ - SpinFactor
1918
+ - JordanMoERouter
1919
+ src/agents/
1920
+ - react.py
1921
+ - ReActAgent
1922
+ - ReActConfig
1923
+ src/tools/
1924
+ - registry.py
1925
+ - ToolRegistry
1926
+ - ToolDefinition
1927
+ - approval.py
1928
+ - ApprovalEngine
1929
+ - RateLimiter
1930
+ ...
1931
+ ```
1932
+
1933
+ ### 7.4 Deployment Guide
1934
+
1935
+ Sections:
1936
+ - Prerequisites (Python 3.11+)
1937
+ - Installation (pip, virtual env)
1938
+ - Configuration (EngineConfig options)
1939
+ - Starting daemon (systemd / Docker / bare metal)
1940
+ - Health checks (HTTP /health endpoint)
1941
+ - Monitoring (WORM ledger queries, metrics)
1942
+ - Troubleshooting (common errors + recovery)
1943
+
1944
+ ### 7.5 Configuration Reference
1945
+
1946
+ ```python
1947
+ # All options documented:
1948
+ @dataclass
1949
+ class EngineConfig:
1950
+ """Complete engine configuration."""
1951
+
1952
+ model_provider: str = "bedrock" # Which model provider
1953
+ model_name: str = "claude-opus"
1954
+
1955
+ continuity_base_dir: Path = Path.home() / ".sovereign" / "continuity"
1956
+ enable_continuity: bool = True
1957
+
1958
+ worm_ledger_path: Path | None = None
1959
+
1960
+ approval_required_for_risky: bool = True
1961
+ rate_limit_policies: dict[str, RateLimitPolicy] = field(default_factory=dict)
1962
+
1963
+ # ... all documented with explanations
1964
+ ```
1965
+
1966
+ ---
1967
+
1968
+ ## 8. CI/CD PIPELINE
1969
+
1970
+ ### Goal
1971
+ GitHub Actions workflow with tests, type checking, security scan, coverage reporting.
1972
+
1973
+ ### 8.1 `.github/workflows/ci.yml` (~150 lines)
1974
+
1975
+ ```yaml
1976
+ name: CI
1977
+
1978
+ on:
1979
+ push:
1980
+ branches: [main, develop]
1981
+ pull_request:
1982
+ branches: [main]
1983
+
1984
+ jobs:
1985
+ test:
1986
+ runs-on: ubuntu-latest
1987
+ strategy:
1988
+ matrix:
1989
+ python-version: ["3.11", "3.12", "3.13"]
1990
+ steps:
1991
+ - uses: actions/checkout@v3
1992
+
1993
+ - name: Set up Python ${{ matrix.python-version }}
1994
+ uses: actions/setup-python@v4
1995
+ with:
1996
+ python-version: ${{ matrix.python-version }}
1997
+
1998
+ - name: Install dependencies
1999
+ run: |
2000
+ pip install -r requirements.txt
2001
+ pip install pytest pytest-cov pyright bandit
2002
+
2003
+ - name: Run pytest
2004
+ run: pytest tests/ --cov=src --cov-report=xml
2005
+
2006
+ - name: Upload coverage
2007
+ uses: codecov/codecov-action@v3
2008
+
2009
+ typecheck:
2010
+ runs-on: ubuntu-latest
2011
+ steps:
2012
+ - uses: actions/checkout@v3
2013
+ - uses: actions/setup-python@v4
2014
+ with:
2015
+ python-version: "3.11"
2016
+
2017
+ - name: Install pyright
2018
+ run: pip install pyright
2019
+
2020
+ - name: Type check
2021
+ run: pyright src/
2022
+
2023
+ nasm_check:
2024
+ runs-on: ubuntu-latest
2025
+ steps:
2026
+ - uses: actions/checkout@v3
2027
+
2028
+ - name: Install NASM
2029
+ run: sudo apt-get install -y nasm
2030
+
2031
+ - name: Syntax check
2032
+ run: nasm -f elf64 -o /dev/null native/*.asm
2033
+
2034
+ c_compile:
2035
+ runs-on: ubuntu-latest
2036
+ steps:
2037
+ - uses: actions/checkout@v3
2038
+
2039
+ - name: Compile C core
2040
+ run: gcc -c -o /dev/null native/ipc_core.c -Wall -Wextra
2041
+
2042
+ security:
2043
+ runs-on: ubuntu-latest
2044
+ steps:
2045
+ - uses: actions/checkout@v3
2046
+ - uses: actions/setup-python@v4
2047
+ with:
2048
+ python-version: "3.11"
2049
+
2050
+ - name: Bandit security scan
2051
+ run: |
2052
+ pip install bandit
2053
+ bandit -r src/ -f json -o bandit-report.json
2054
+
2055
+ - name: Upload security report
2056
+ uses: actions/upload-artifact@v3
2057
+ with:
2058
+ name: bandit-report
2059
+ path: bandit-report.json
2060
+ ```
2061
+
2062
+ ### 8.2 Pre-Commit Hooks (`.pre-commit-config.yaml`)
2063
+
2064
+ ```yaml
2065
+ repos:
2066
+ - repo: https://github.com/psf/black
2067
+ rev: 23.1.0
2068
+ hooks:
2069
+ - id: black
2070
+ language_version: python3.11
2071
+
2072
+ - repo: https://github.com/PyCQA/isort
2073
+ rev: 5.12.0
2074
+ hooks:
2075
+ - id: isort
2076
+ args: [--profile=black]
2077
+
2078
+ - repo: https://github.com/RobertCraigie/pyright-python
2079
+ rev: v1.1.280
2080
+ hooks:
2081
+ - id: pyright
2082
+ ```
2083
+
2084
+ ---
2085
+
2086
+ ## IMPLEMENTATION PRIORITY
2087
+
2088
+ ### Phase 1: Crash Prevention (Week 1–2)
2089
+ 1. Edge cases (3.1–3.10) — all error handling
2090
+ 2. Input validation (4.5)
2091
+ 3. Unit tests (5.1) — at least 50% coverage
2092
+
2093
+ ### Phase 2: Security (Week 2–3)
2094
+ 1. PathJail extensions (3.1)
2095
+ 2. SSRFGuard extensions (3.2)
2096
+ 3. Rate limiting (4.2)
2097
+ 4. Cost tracking (4.3)
2098
+ 5. Emergency kill switch (4.4)
2099
+ 6. Security tests
2100
+
2101
+ ### Phase 3: Features (Week 3–4)
2102
+ 1. Terminal IDE (1.1–1.3)
2103
+ 2. Accessibility (2.1–2.3)
2104
+ 3. Integration tests (5.2)
2105
+
2106
+ ### Phase 4: Polish (Week 4–6)
2107
+ 1. Performance optimization (6)
2108
+ 2. Property-based tests (5.3)
2109
+ 3. Documentation (7)
2110
+ 4. CI/CD pipeline (8)
2111
+
2112
+ ---
2113
+
2114
+ ## DEFINITION OF DONE
2115
+
2116
+ - [ ] All code passes pytest (2000+ lines coverage)
2117
+ - [ ] All public methods have docstrings (format: § 7.1)
2118
+ - [ ] CI/CD pipeline green on all Python 3.11–3.13
2119
+ - [ ] Security scan (bandit) passes
2120
+ - [ ] Type check (pyright) passes with 0 errors
2121
+ - [ ] WORM append-only invariant holds (proven by tests)
2122
+ - [ ] Routing latency < 10ms (profiled)
2123
+ - [ ] PathJail blocks all 20 traversal attacks (test cases)
2124
+ - [ ] Terminal IDE renders without flicker
2125
+ - [ ] Accessibility complies with WCAG 2.1 AA (screen reader compatible)
2126
+ - [ ] No external dependencies beyond Python 3.11 stdlib
2127
+ - [ ] Deployment guide tested on 3 platforms (Linux/macOS/Windows)
2128
+
2129
+ ---
2130
+
2131
+ ## CRITICAL NOTES FOR CODEX
2132
+
2133
+ 1. **No external deps** — Use only Python stdlib. No pip packages except dev tools (pytest, pyright, etc).
2134
+ 2. **WORM immutability** — Every test must verify chain integrity. If one byte changes, chain breaks.
2135
+ 3. **Continuity restart** — Test that agent survives daemon restart and can resume from exact step.
2136
+ 4. **PathJail escape** — Try 50+ creative escapes (UNC paths, drive letters, junctions on Windows). All must fail.
2137
+ 5. **Async/await** — All I/O must be async. No blocking calls in agent loop.
2138
+ 6. **Error messages** — Plain English. No jargon. Include recovery steps.
2139
+ 7. **Performance** — Profile before optimizing. Targets are hard (10ms routing, 100us IPC).
2140
+ 8. **Docstrings** — Non-negotiable. Every public function must be documented.
2141
+
2142
+ ---
2143
+
2144
+ **End of Specification**
2145
+
2146
+ Document version: 1.0
2147
+ Generated: 2026-08-06
2148
+ Scope: Full production hardening of Sovereign Python LLM Engine
2149
+ Target audience: Codex AI code implementation agent
README.md ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```
2
+ ____ _ ______ _
3
+ / ___| _____ _____ _ __ ___(_) __ _ _ __ | ____| _ __ __ _(_)_ __ ___
4
+ \___ \ / _ \ \ / / _ \ '__/ _ \ |/ _` | '_ \ | _| | '_ \ / _` | | '_ \ / _ \
5
+ ___) | (_) \ V / __/ | | __/ | (_| | | | | | |___| | | | (_| | | | | | __/
6
+ |____/ \___/ \_/ \___|_| \___|_|\__, |_| |_| |_____|_| |_|\__, |_|_| |_|\___|
7
+ |___/ |___/ v2.0
8
+ ```
9
+
10
+ ![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue)
11
+ ![C Win32](https://img.shields.io/badge/C-Win32%20Native-orange)
12
+ ![Lines](https://img.shields.io/badge/Lines-50%2C444-green)
13
+ ![License](https://img.shields.io/badge/License-BSL%201.1-yellow)
14
+ ![Storage](https://img.shields.io/badge/Storage-Binary%20WORM-red)
15
+ ![Routing](https://img.shields.io/badge/Routing-Jordan%20Algebra-purple)
16
+
17
+ ---
18
+
19
+ ## What This Is
20
+
21
+ A **complete LLM agent development environment**: native C desktop IDE connected to a Python inference engine via memory-mapped IPC.
22
+
23
+ This is not a wrapper. Not a LangChain clone. Not vibe-coded. This is:
24
+
25
+ - A **C Win32 IDE** with text editor, ConPTY terminal, LSP, DAP debugging, Direct2D rendering, git integration — 47 source files, 4,459 lines of C
26
+ - A **Python LLM engine** with custom MoE routing, binary storage, four-paradigm continuity, 34 tools, ReAct agents — 87 modules, 38,576 lines
27
+ - A **NASM x86-64 assembly layer** with native runtime, QRA tensor ops, Jordan block computation, NAND kernel, IPC dispatcher — 7 files, 7,019 lines
28
+ - A **C IPC dispatcher** with mmap ring buffer, opcode jump table, 50μs polling — 390 lines
29
+
30
+ **50,444 lines of production code. 142 source files. Pure stdlib Python + raw C + NASM.**
31
+
32
+ ---
33
+
34
+ ## Table of Contents
35
+
36
+ - [The System](#the-system)
37
+ - [Quick Start](#quick-start)
38
+ - [The IDE](#the-ide)
39
+ - [The Engine](#the-engine)
40
+ - [Machine Code Layer](#machine-code-layer)
41
+ - [Routing Pipeline](#routing-pipeline)
42
+ - [Continuity Layer](#continuity-layer)
43
+ - [Tool System](#tool-system)
44
+ - [Security Architecture](#security-architecture)
45
+ - [Configuration](#configuration)
46
+ - [The Mathematics](#the-mathematics)
47
+ - [Origin: The DSL](#origin-the-dsl)
48
+ - [Papers](#papers)
49
+ - [Line Count](#line-count)
50
+
51
+ ---
52
+
53
+ ## The System
54
+
55
+ ```mermaid
56
+ graph TB
57
+ subgraph "C Win32 IDE (4,459 lines)"
58
+ EDITOR[Editor + Buffer]
59
+ TERM[ConPTY Terminal]
60
+ LSP[LSP Client]
61
+ DAP[DAP Debugger]
62
+ UI[UI Layout + Direct2D]
63
+ GIT[Git Integration]
64
+ FCL[FCL Evaluator]
65
+ end
66
+
67
+ subgraph "IPC Bridge"
68
+ HTTP[HTTP Bridge :19000]
69
+ MMAP[mmap Ring Buffer]
70
+ PIPE[Named Pipe Protocol]
71
+ end
72
+
73
+ subgraph "Python Engine (38,576 lines)"
74
+ ROUTE[11-Stage Routing Pipeline]
75
+ AGENT[ReAct Agent + Shadow Observer]
76
+ TOOLS[34 Tools x 9 Namespaces]
77
+ CONT[4-Paradigm Continuity]
78
+ WORM[Binary WORM Storage]
79
+ QRA[QRA Router - 6 Glyphs]
80
+ end
81
+
82
+ subgraph "Native Layer (7,409 lines)"
83
+ ASM[NASM x86-64 Runtime]
84
+ IPC_C[C IPC Dispatcher]
85
+ VM[Stack VM + NAND Opcodes]
86
+ JORDAN_ASM[Jordan Blocks SSE2/AVX2]
87
+ end
88
+
89
+ EDITOR --> HTTP
90
+ TERM --> PIPE
91
+ HTTP --> ROUTE
92
+ MMAP --> IPC_C
93
+ ROUTE --> AGENT
94
+ AGENT --> TOOLS
95
+ AGENT --> CONT
96
+ AGENT --> WORM
97
+ IPC_C --> ASM
98
+ ASM --> JORDAN_ASM
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Quick Start
104
+
105
+ ```bash
106
+ git clone https://github.com/SNAPKITTYWEST/sovereign-engine-v2.git
107
+ cd sovereign-engine-v2
108
+
109
+ # Run the engine (zero dependencies)
110
+ python -c "
111
+ import asyncio
112
+ from src.sovereign import SovereignEngine, EngineConfig
113
+
114
+ async def main():
115
+ engine = SovereignEngine(EngineConfig())
116
+ result = await engine.run('Write a fibonacci function')
117
+ print(result)
118
+ engine.shutdown()
119
+
120
+ asyncio.run(main())
121
+ "
122
+ ```
123
+
124
+ ```bash
125
+ # Build the C IDE (Windows — requires CMake + MSVC)
126
+ cd ide/native
127
+ cmake -B build -G "Visual Studio 17 2022"
128
+ cmake --build build --config Release
129
+ ./build/Release/sovereign-ide.exe
130
+ ```
131
+
132
+ ---
133
+
134
+ ## The IDE
135
+
136
+ **Location:** `ide/native/` — 47 C source files, 4,459 lines
137
+
138
+ This is a native Win32 application. No Electron. No JavaScript. No web view. Direct2D GPU rendering, ConPTY pseudo-terminal, Win32 message loop.
139
+
140
+ | Directory | Purpose | Key Files |
141
+ |-----------|---------|-----------|
142
+ | `core/` | Memory arena, event system, strings | `arena.c` (pool allocator), `events.c` (pub/sub) |
143
+ | `editor/` | Gap buffer text editor | `buffer.c` (insert/delete O(1)), `document.c` (file model) |
144
+ | `terminal/` | Embedded terminal | `conpty.c` (Windows ConPTY API) |
145
+ | `ui/` | Layout engine | `layout.c` (split panes), `status_bar.h`, `project_tree.h` |
146
+ | `bridge/` | Engine connection | `bridge_http.c` (HTTP client to Python :19000) |
147
+ | `chat/` | Agent interaction | `pipe_client.c` (named pipe), `protocol.c` (message framing) |
148
+ | `lsp/` | Language intelligence | `client.c` (Language Server Protocol) |
149
+ | `dap/` | Debugging | Debug Adapter Protocol integration |
150
+ | `git/` | Version control | `repository.c` (status, diff, commit) |
151
+ | `fcl/` | Command language | `evaluator.c` (Formal Command Language interpreter) |
152
+ | `graphics/` | Rendering | `d2d_renderer.h` (Direct2D hardware-accelerated) |
153
+ | `platform/windows/` | OS layer | `application.c`, `window.c`, `shell.c` |
154
+ | `build/` | Build system | `cmake_runner.c` (invoke cmake from IDE) |
155
+
156
+ ### How the IDE Talks to the Engine
157
+
158
+ ```
159
+ ┌────────────────┐ ┌──────────────────┐ ┌─────────────────┐
160
+ │ C IDE │ HTTP │ Python Bridge │ │ Engine │
161
+ │ │────────>│ :19000 │───────>│ Routing │
162
+ │ bridge_http.c │ JSON │ http_server.py │ │ Agent │
163
+ │ │<────────│ │<───────│ Tools │
164
+ └────────────────┘ └──────────────────┘ └─────────────────┘
165
+
166
+ ┌────────────────┐ ┌──────────────────┐
167
+ │ C IDE │ mmap │ C IPC Core │ (native tools — no HTTP, no JSON)
168
+ │ │────────>│ ipc_core.c │ Latency: ~100μs round trip
169
+ │ (direct call) │<────────│ opcode dispatch │
170
+ └────────────────┘ └──────────────────┘
171
+
172
+ ┌────────────────┐ ┌──────────────────┐
173
+ │ C IDE │ pipe │ Agent Chat │ (streaming agent responses)
174
+ │ pipe_client.c │────────>│ python_daemon │
175
+ │ │<────────│ :19002 TCP │
176
+ └────────────────┘ └──────────────────┘
177
+ ```
178
+
179
+ ---
180
+
181
+ ## The Engine
182
+
183
+ **Location:** `src/` — 87 Python modules, 38,576 lines
184
+
185
+ Every module is **pure Python 3.11+ stdlib**. Zero external dependencies. No pip install needed.
186
+
187
+ | Package | Lines | What It Does |
188
+ |---------|-------|-------------|
189
+ | `src/routing/` | 2,800 | 11-stage MoE pipeline: regex → AST → symbolic graph → Jordan transform → Jacobian → constraints → sparse activation → NAND filter → dispatch → merge |
190
+ | `src/runtime/machine/` | 9,251 | CPython bytecode assembler, .pyc marshal codec, ctypes C bridge, SOVEREIGN_IR binary format, stack VM with NAND opcodes, x86-64 machine code generator |
191
+ | `src/tools/` | 3,500 | 34 tools (filesystem, code, git, database, documents, web, embeddings, audio, pytorch), IPC router, opcode registry, approval engine, supervisor |
192
+ | `src/continuity/` | 1,536 | Env bitmask, seed chain, inode flags, shared memory, unified manager |
193
+ | `src/agents/` | 1,500 | ReAct loop (thought→action→observation→reflection), shadow observer, MCTS |
194
+ | `src/retrieval/` | 1,500 | Semantic chunker (6 strategies), vector store, RAG pipeline, parallel ingest |
195
+ | `src/daemon/` | 823 | Asyncio TCP daemon (:19002), swarm orchestration (fan_out, map_reduce, race) |
196
+ | `src/bridge/` | 900 | HTTP server (:19000), stdio JSON-RPC, routing trace collector, key manager |
197
+ | `src/core/` | 700 | Binary WORM storage, evidence ledger, Ed25519 crypto, path jail, SSRF guard |
198
+ | `src/runtime/providers/` | 600 | Bedrock, OpenRouter, Ollama, Anthropic, OpenAI adapters + QRA router |
199
+ | `src/models/` | 500 | Pydantic entities, state machines |
200
+ | `src/scanner/` | 400 | AST analyzer, dependency graph builder |
201
+ | `src/inference/` | 300 | Quantum MoE (SpinFactor composition) |
202
+ | `src/mcp/` | 250 | Model Context Protocol server |
203
+ | `src/cli/` | 200 | Command-line interface |
204
+
205
+ ---
206
+
207
+ ## Machine Code Layer
208
+
209
+ **Location:** `src/runtime/machine/` (Python) + `native/asm/` (NASM)
210
+
211
+ This is not a toy abstraction. These are real implementations:
212
+
213
+ ### Python Machine Code (`src/runtime/machine/` — 9,251 lines)
214
+
215
+ | Module | Lines | What It Actually Does |
216
+ |--------|-------|----------------------|
217
+ | `bytecode_assembler.py` | 1,711 | Emits real CPython opcodes (LOAD_FAST, CALL_FUNCTION, etc). Produces executable code objects. |
218
+ | `marshal_codec.py` | 1,435 | Reads and writes actual .pyc binary format (magic number, flags, code objects, consts table). |
219
+ | `ctypes_bridge.py` | 970 | Builds C struct definitions from Python, manages MemoryArena for native allocations. |
220
+ | `binary_ir.py` | 1,492 | SOVEREIGN_IR format: 32-byte fixed-width node records. Opcode + flags + operands + type tag. Scannable without parsing. |
221
+ | `vm_executor.py` | 1,680 | Stack-based virtual machine. 40+ opcodes including custom NAND, JORDAN_MUL, ENTROPY_CHECK. Runs SOVEREIGN_IR bytecode. |
222
+ | `machine_code_gen.py` | 1,104 | Emits raw x86-64 bytes. REX prefixes, ModR/M encoding, register allocation. Produces executable buffers via mmap+mprotect. |
223
+ | `dsl_validator.py` | 658 | Validates all DSL constraints: Boolean kernel (NAND truth table), entropy ≤ 0.20, trust axiom, glyph injectivity, DAG acyclicity. Blake2b proof hash. |
224
+
225
+ ### NASM x86-64 Assembly (`native/asm/` — 7,019 lines)
226
+
227
+ | File | Lines | What It Actually Does |
228
+ |------|-------|----------------------|
229
+ | `sovereign_runtime.asm` | 2,459 | Main runtime: WORM append (struct pack → write syscall), ring buffer management, signal handling, mmap allocation |
230
+ | `ipc_dispatcher.asm` | 1,071 | Polls mmap region, reads 16-bit opcode, jump table dispatch (34 entries), writes response struct back |
231
+ | `qra_tensor.asm` | 905 | 6x6 matrix multiply (packed SSE2), QLG balance check (x₀²+x₁²+x₂²=1 over ℤ), glyph classification |
232
+ | `nand_kernel.asm` | 790 | NAND gate truth table in SIMD, builds NOT→AND→OR→IMPLIES→EQUAL per DSL BooleanKernel spec |
233
+ | `jordan_blocks.asm` | 770 | SpinFactor product in SSE2/AVX2: scalar×scalar + dot product, scalar×vector + scalar×vector. Batch 4 elements. |
234
+ | `entropy_gate.asm` | 552 | Shannon entropy H=-Σp·ln(p) via x87 FPU. Compare against 0.20 threshold. Set carry flag on violation. |
235
+ | `sovereign_link.asm` | 472 | Public symbol table: exports all above as callable from C (System V ABI on Linux, MS x64 on Windows) |
236
+
237
+ ---
238
+
239
+ ## Routing Pipeline
240
+
241
+ 11 stages. Every stage has a mathematical role. This is not a prompt chain.
242
+
243
+ ```
244
+ User Input
245
+
246
+ ├─── Stage 1: Regex Parser ──────── Tokenize. Strip dangerous patterns.
247
+ │ Blocklist: eval, exec, import, __,
248
+ │ base64, <script, <!ENTITY, SYSTEM
249
+
250
+ ├─── Stage 2: AST Builder ───────── Build INVERTED syntax tree.
251
+ │ Structural nodes: routing_weight=1.0
252
+ │ Payload leaves: routing_weight=0.0
253
+ │ Payloads CANNOT propagate upward.
254
+
255
+ ├─── Stage 3: Symbolic Graph ────── Adjacency matrix of signal flow.
256
+ │ Nodes = intent categories.
257
+ │ Edges = co-occurrence weights.
258
+
259
+ ├─── Stage 4: Jordan Transform ──── SpinFactor composition.
260
+ │ (α,v) ∘ (β,w) = (αβ+⟨v,w⟩, αw+βv)
261
+ │ Non-associative: topology matters.
262
+ │ Gershgorin eigenvalue bounds.
263
+
264
+ ├─── Stage 5: Jacobian Lens ─────── ∂routing/∂signal via finite diffs.
265
+ │ Sensitivity analysis: which input
266
+ │ features drive which expert weights.
267
+
268
+ ├─── Stage 6: Constraint Eval ───── Boolean expert mask.
269
+ │ Spectral radius must be < 10.
270
+ │ Entropy must be ≤ 0.20 nats.
271
+
272
+ ├─── Stage 7: Sparse Activation ─── Top-k expert selection.
273
+ │ Only k experts get nonzero weight.
274
+ │ Rest are zeroed (not softmaxed).
275
+
276
+ ├─── Stage 8: NAND Filter ──────── Conflict suppression.
277
+ │ NAND(A,B) = suppress lower-weight
278
+ │ when both experts claim same input.
279
+
280
+ ├─── Stage 9: Agent Dispatch ────── Concurrent asyncio execution.
281
+ │ Each active expert runs in parallel.
282
+ │ Timeout per expert (configurable).
283
+
284
+ ├─── Stage 10: Merge Output ─────── 4 strategies: concatenate, vote,
285
+ │ weighted_sum, first_success.
286
+
287
+ └─── Stage 11: WORM Seal ────────── Blake2b hash of routing decision.
288
+ Ed25519 signature. Append to chain.
289
+ Decision is now immutable.
290
+ ```
291
+
292
+ ---
293
+
294
+ ## Continuity Layer
295
+
296
+ The agent doesn't lose state. Ever. Four independent persistence mechanisms sync on every state transition:
297
+
298
+ | # | Paradigm | Storage | Speed | What Survives |
299
+ |---|----------|---------|-------|---------------|
300
+ | 1 | **Env bitmask** | `os.environ` (64-bit packed) | Instant | `os.execv` hot restart — same PID, new binary |
301
+ | 2 | **Seed chain** | Blake2b derivation (24 bytes total) | Instant | Full history compressed to one hash. Deterministic replay from any point. |
302
+ | 3 | **Inode flags** | Zero-byte files + `stat()` | Kernel cache | Process crash. Kernel dcache survives OOM kill. |
303
+ | 4 | **Shared memory** | ctypes struct (4KB mmap block) | RAM speed | Cross-process visibility. No serialization. |
304
+
305
+ If ANY of the four has state, the agent resumes. `ContinuityManager` unifies all four behind one API:
306
+
307
+ ```python
308
+ cm = ContinuityManager(base_dir=Path("~/.sovereign"), agent_id="react_1")
309
+ cm.transition("IDLE", "THINKING") # updates all 4 backends
310
+ cm.advance_op("THINK:plan task") # advances seed chain
311
+ cm.increment_step() # syncs step across all 4
312
+ snapshot = cm.snapshot() # reads from fastest available
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Tool System
318
+
319
+ 34 tools with native opcode dispatch:
320
+
321
+ <details>
322
+ <summary><strong>All 34 tools with opcodes (click to expand)</strong></summary>
323
+
324
+ | Opcode | Namespace | Tool | Description |
325
+ |--------|-----------|------|-------------|
326
+ | `0x0001` | filesystem | read | Read file contents (PathJail enforced) |
327
+ | `0x0002` | filesystem | write | Write file (PathJail enforced) |
328
+ | `0x0003` | filesystem | list | List directory |
329
+ | `0x0004` | filesystem | delete | Remove file |
330
+ | `0x0005` | filesystem | move | Move/rename |
331
+ | `0x0006` | filesystem | search | Pattern search (ripgrep-style) |
332
+ | `0x0007` | code | execute | Run code in sandbox |
333
+ | `0x0008` | code | analyze | AST analysis + symbol extraction |
334
+ | `0x0009` | git | status | Repository status |
335
+ | `0x000A` | git | diff | Show changes |
336
+ | `0x000B` | git | commit | Create commit |
337
+ | `0x000C` | git | log | Commit history |
338
+ | `0x000D` | git | branch | Branch operations |
339
+ | `0x000E` | git | checkout | Switch branch |
340
+ | `0x000F` | git | merge | Merge branches |
341
+ | `0x0010` | git | stash | Stash changes |
342
+ | `0x0011` | git | remote | Remote operations |
343
+ | `0x0012` | git | tag | Tag management |
344
+ | `0x0013` | database | query | Execute SQL (parameterized) |
345
+ | `0x0014` | database | schema | Schema introspection |
346
+ | `0x0015` | database | migrate | Run migrations |
347
+ | `0x0016` | documents | parse_pdf | Extract PDF text |
348
+ | `0x0017` | documents | parse_docx | Extract DOCX content |
349
+ | `0x0018` | documents | render_md | Render Markdown |
350
+ | `0x0019` | documents | parse_html | Extract HTML text |
351
+ | `0x001A` | web | search | Web search (SSRFGuard enforced) |
352
+ | `0x001B` | web | fetch | HTTP fetch (SSRFGuard enforced) |
353
+ | `0x001C` | web | scrape | Page scraping |
354
+ | `0x001D` | embeddings | encode | Generate embeddings |
355
+ | `0x001E` | embeddings | search | Similarity search |
356
+ | `0x001F` | audio | transcribe | Speech-to-text |
357
+ | `0x0020` | audio | synthesize | Text-to-speech |
358
+ | `0x0021` | pytorch | inference | Model inference |
359
+ | `0x0022` | pytorch | check_cuda | GPU availability |
360
+
361
+ </details>
362
+
363
+ **Two dispatch paths:**
364
+ - **HTTP** (universal): IDE → JSON POST → parse → handler → JSON response (~5ms)
365
+ - **Native mmap** (local): IDE → write opcode to ring buffer → C dispatcher reads → handler → write response (~100μs)
366
+
367
+ The IPC ring buffer is a 64KB mmap region shared between the C IDE and the Python engine. The C dispatcher (`native/dispatcher/ipc_core.c`) polls at 50μs intervals and dispatches via a jump table indexed by the 16-bit opcode.
368
+
369
+ ---
370
+
371
+ ## Security Architecture
372
+
373
+ | Layer | Threat | Defense |
374
+ |-------|--------|---------|
375
+ | **PathJail** | `../../etc/passwd`, symlink escape, null bytes | Resolve path → check against allowed roots → reject if outside |
376
+ | **SSRFGuard** | `http://169.254.169.254/metadata`, `http://10.0.0.1` | Block all private IPs, link-local, metadata endpoints |
377
+ | **Inverted AST** | Payload injection into routing decisions | Structural nodes (weight=1) route. Payload leaves (weight=0) can NEVER propagate upward. |
378
+ | **NAND Filter** | Two experts both claiming same input (conflict) | NAND suppresses the lower-weight expert. Prevents split-brain. |
379
+ | **Binary WORM** | JSONL injection, newline attacks, log corruption | 152-byte struct headers. No text parsing anywhere. Append-only file mode. |
380
+ | **ERE Gates** | Secret leakage, eval injection, infinite loops, telemetry | P1: no hardcoded secrets. P2: no eval/exec/import. P3: no `while True` without break. P4: no analytics. P5: SHA-256 audit seal. |
381
+ | **Approval Engine** | Unauthorized tool execution | Risk-level classification. High-risk tools require explicit approval. |
382
+ | **Chain Verification** | Tampered ledger entries | Every WORM record hashes the previous record. Break one → break all downstream. |
383
+
384
+ ---
385
+
386
+ ## Configuration
387
+
388
+ ```python
389
+ from src.sovereign import SovereignEngine, EngineConfig
390
+ from pathlib import Path
391
+
392
+ engine = SovereignEngine(EngineConfig(
393
+ allowed_roots=[Path.cwd(), Path("/data")],
394
+ ledger_path=Path("./sovereign.worm"),
395
+ continuity_dir=Path.home() / ".sovereign" / "continuity",
396
+ max_steps=15,
397
+ enable_shadow=True,
398
+ enable_ipc=True,
399
+ agent_id="my_agent",
400
+ ))
401
+ ```
402
+
403
+ | Option | Type | Default | Effect |
404
+ |--------|------|---------|--------|
405
+ | `allowed_roots` | `list[Path]` | `[cwd()]` | PathJail boundaries — filesystem tools cannot access anything outside |
406
+ | `ledger_path` | `Path` | `./sovereign.worm` | Binary WORM ledger location |
407
+ | `continuity_dir` | `Path` | `~/.sovereign/continuity` | Where the 4 paradigms store state |
408
+ | `max_steps` | `int` | `15` | Hard limit on agent steps (prevents runaway) |
409
+ | `enable_shadow` | `bool` | `True` | Shadow observer watches for anomalies (cost spikes, loops, failures) |
410
+ | `enable_ipc` | `bool` | `True` | Enable native mmap dispatch (disable for pure-HTTP mode) |
411
+ | `agent_id` | `str` | `"sovereign_main"` | Identity for continuity (different IDs = separate state) |
412
+
413
+ ---
414
+
415
+ ## The Mathematics
416
+
417
+ ### Jordan Algebra — SpinFactor J(n)
418
+
419
+ The routing gate uses Jordan algebra instead of softmax. This is the same algebra Pascual Jordan developed in 1933 to formalize quantum mechanics observables.
420
+
421
+ **Elements:** `(α, v)` where `α ∈ ℝ` (scalar confidence), `v ∈ ℝⁿ` (signal vector)
422
+
423
+ **Product:** `(α,v) ∘ (β,w) = (αβ + ⟨v,w⟩, αw + βv)`
424
+
425
+ **Why this matters for routing:**
426
+
427
+ 1. **Non-associative composition**: `(A∘B)∘C ≠ A∘(B∘C)` — different agent grouping topologies produce mathematically different routing outcomes. We search over topologies.
428
+ 2. **Fixed-point convergence**: Iterating `x ↦ x∘x` converges to idempotents (`e∘e = e`). These ARE the stable routing attractors.
429
+ 3. **Spectral decomposition**: `x = λ₊c₊ + λ₋c₋` where `λ± = α ± ‖v‖`. Provably unique expert assignment.
430
+ 4. **Spectral gap** = `2‖v‖` = separation between top experts. Bigger gap = more decisive routing.
431
+
432
+ ### QRA Tensor — Quantum Routing Algebra
433
+
434
+ 6×6 deterministic tensor. Shannon entropy H = 0 nats. No randomness.
435
+
436
+ | Glyph | Maps To | Signal |
437
+ |-------|---------|--------|
438
+ | Π (Pi) | Reasoning models | "explain", "why", "analyze" |
439
+ | Γ (Gamma) | Fast generation | "write", "create", "draft" |
440
+ | Δ (Delta) | Domain-specific | "sql", "medical", "legal" |
441
+ | Λ (Lambda) | Code models | "function", "implement", "debug" |
442
+ | Ω (Omega) | Orchestration | "plan", "coordinate", "multi-step" |
443
+ | Ψ (Psi) | Verification | "prove", "verify", "test" |
444
+
445
+ ---
446
+
447
+ ## Origin: The DSL
448
+
449
+ The Python engine (38,576 lines) + NASM assembly (7,019 lines) were generated from a single **HyperKittyConstraintDSL** prompt. Three agents coordinated by the DSL constraints. See [ARCHITECTURE.md](ARCHITECTURE.md) for the display template.
450
+
451
+ The C IDE was hand-built separately.
452
+
453
+ The DSL defines:
454
+ - Boolean kernel (all gates from NAND)
455
+ - Glyph type system (7 semantic types)
456
+ - Agent invariants (trust + entropy bounds)
457
+ - DAG structure (acyclic routing graph)
458
+ - Proof output (Blake2b hash commitment)
459
+ - Transformation engine (regex patterns + validation)
460
+
461
+ ---
462
+
463
+ ## Papers
464
+
465
+ | DOI | Title |
466
+ |-----|-------|
467
+ | [10.5281/zenodo.20678420](https://doi.org/10.5281/zenodo.20678420) | Attention Exhaustion Attacks — 0% detection rate |
468
+ | [10.5281/zenodo.21144425](https://doi.org/10.5281/zenodo.21144425) | Resonance Block Trust Deeds — capture spectrum |
469
+ | [10.5281/zenodo.21132094](https://doi.org/10.5281/zenodo.21132094) | Sovereign Compute Architecture |
470
+ | [10.5281/zenodo.21349277](https://doi.org/10.5281/zenodo.21349277) | Gates Normalization Constraint — simplex is structural |
471
+ | [10.5281/zenodo.21351461](https://doi.org/10.5281/zenodo.21351461) | NAND Decomposition — attention is NAND-complete |
472
+ | [10.5281/zenodo.21443609](https://doi.org/10.5281/zenodo.21443609) | Jordan Spectral Transformer — phi-weighted routing |
473
+ | [10.5281/zenodo.21727363](https://doi.org/10.5281/zenodo.21727363) | PAR-011 Jacobian via Jordan Algebras |
474
+ | [10.5281/zenodo.21268911](https://doi.org/10.5281/zenodo.21268911) | GKN I4 Quartic Invariant and E7 Symmetry |
475
+
476
+ Unified: [The Sovereign Stack](https://snapkittywest.github.io/hyperkitty/papers/sovereign-stack-unified.pdf) — 26 pages, Lean 4.
477
+
478
+ ---
479
+
480
+ ## Line Count
481
+
482
+ | Component | Language | Files | Lines |
483
+ |-----------|----------|-------|-------|
484
+ | Engine core | Python 3.11 | 87 | 38,576 |
485
+ | NASM runtime | x86-64 Assembly | 7 | 7,019 |
486
+ | C Win32 IDE | C (Win32 API) | 47 | 4,459 |
487
+ | C IPC dispatcher | C | 1 | 390 |
488
+ | **Total** | **3 languages** | **142** | **50,444** |
489
+
490
+ ---
491
+
492
+ ## Documentation
493
+
494
+ | Guide | What You Learn |
495
+ |-------|---------------|
496
+ | [Architecture](ARCHITECTURE.md) | DSL proof-of-concept, how the engine was generated |
497
+ | [Getting Started](docs/GETTING_STARTED.md) | Install, configure, first task |
498
+ | [Configuration](docs/CONFIGURATION.md) | Every option explained with examples |
499
+ | [Routing](docs/ROUTING.md) | 11-stage pipeline deep dive, tuning, custom experts |
500
+ | [Tools](docs/TOOLS.md) | All 34 tools, custom registration, IPC opcodes |
501
+ | [Continuity](docs/CONTINUITY.md) | 4 paradigms, crash recovery, replay |
502
+ | [Security](docs/SECURITY.md) | PathJail, SSRF, WORM, ERE gates |
503
+ | [Production Hardening](PRODUCTION_HARDENING.md) | Terminal IDE, accessibility, edge cases, testing, CI |
504
+
505
+ ---
506
+
507
+ ## License
508
+
509
+ BSL 1.1 → MIT 2029-01-01
510
+
511
+ SnapKitty / SNAPKITTYWEST / 2026
SPRINT_PLAN.md ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 3-Phase Sprint Plan — Sovereign Engine v2
2
+
3
+ ## Current State (Audit)
4
+
5
+ **Tracked:** 137 files on remote
6
+ **Untracked (needs commit):** ide/, docs/, routing_trace.py, qra_router.py, tests/
7
+ **Broken:** ere.py had syntax error (FIXED locally, not pushed)
8
+ **Missing docs:** ROUTING.md, TOOLS.md, CONTINUITY.md, SECURITY.md
9
+
10
+ ---
11
+
12
+ ## PHASE 1: Structural Integrity (This Sprint)
13
+
14
+ Goal: Every file compiles, imports resolve, repo structure is clean.
15
+
16
+ - [x] Fix ere.py syntax error (smart quotes → ASCII)
17
+ - [ ] Full syntax check — 0 errors across all .py files
18
+ - [ ] Commit IDE (ide/native/ — 47 C files, 4,459 lines)
19
+ - [ ] Commit Gap 2 (qra_router.py — QRA 6-glyph routing)
20
+ - [ ] Commit Gap 5 (routing_trace.py — trace collector for IDE bridge)
21
+ - [ ] Commit docs/ (GETTING_STARTED.md, CONFIGURATION.md)
22
+ - [ ] Remove Codex planning artifacts (AGENT_BRIEF, gaps/, architecture/)
23
+ - [ ] Verify all __init__.py exports resolve
24
+ - [ ] Push to remote — single clean commit
25
+
26
+ **Acceptance:** `python -c "from src import SovereignEngine"` works. Zero syntax errors. No stale files.
27
+
28
+ ---
29
+
30
+ ## PHASE 2: Documentation + User Guides (Next Sprint)
31
+
32
+ Goal: Someone can clone this repo and understand + use it without asking.
33
+
34
+ - [ ] docs/ROUTING.md — 11-stage pipeline explained, tuning, custom experts
35
+ - [ ] docs/TOOLS.md — All 34 tools, registration, IPC, supervisor
36
+ - [ ] docs/CONTINUITY.md — 4 paradigms, crash recovery, replay
37
+ - [ ] docs/SECURITY.md — PathJail, SSRF, WORM, ERE, inverted AST
38
+ - [ ] docs/IDE.md — How to build the C IDE, connect to engine, use ConPTY
39
+ - [ ] docs/MACHINE_CODE.md — What the bytecode assembler / VM / x86 gen actually do
40
+ - [ ] README cross-references all docs (already done in new README)
41
+ - [ ] Each doc has runnable code examples
42
+
43
+ **Acceptance:** A developer can read docs/ top-to-bottom and set up the full system.
44
+
45
+ ---
46
+
47
+ ## PHASE 3: Version Control + Release + Packaging (Final Sprint)
48
+
49
+ Goal: Proper tagged release with installable package.
50
+
51
+ - [ ] pyproject.toml verified (already exists)
52
+ - [ ] `python -m build` produces .whl
53
+ - [ ] GitHub Release v2.0.0 with changelog
54
+ - [ ] Git tag v2.0.0
55
+ - [ ] LICENSE file (BSL 1.1)
56
+ - [ ] CHANGELOG.md
57
+ - [ ] .github/workflows/ci.yml (Python 3.11-3.13 matrix, pytest, pyright)
58
+ - [ ] requirements.txt is empty (stdlib only — but document optional deps)
59
+ - [ ] Verify `pip install .` works from clean clone
60
+
61
+ **Acceptance:** `pip install .` → `sovereign --help` works. Tagged release on GitHub.
62
+
63
+ ---
64
+
65
+ ## Rules
66
+
67
+ 1. No rushing. Every file gets syntax-checked before commit.
68
+ 2. No planning docs in the repo. Only code, docs, and config.
69
+ 3. Line counts in README must be verified (`wc -l`) not guessed.
70
+ 4. If an agent writes something broken, I fix it or rewrite it. No shipping broken code.
71
+ 5. One clean commit per phase. Not 50 micro-commits.
docs/CONFIGURATION.md ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Configuration Reference
2
+
3
+ Complete guide to all Sovereign Engine configuration options.
4
+
5
+ ## EngineConfig Dataclass
6
+
7
+ The `EngineConfig` controls all engine behavior. Pass it to `SovereignEngine()`:
8
+
9
+ ```python
10
+ from pathlib import Path
11
+ from src.sovereign import EngineConfig, SovereignEngine
12
+
13
+ config = EngineConfig(
14
+ allowed_roots=[Path("/safe/paths")],
15
+ ledger_path=Path("./sovereign.worm"),
16
+ continuity_dir=Path.home() / ".sovereign" / "continuity",
17
+ max_steps=15,
18
+ enable_shadow=True,
19
+ enable_ipc=True,
20
+ agent_id="sovereign_main"
21
+ )
22
+ engine = SovereignEngine(config)
23
+ ```
24
+
25
+ ### Configuration Options
26
+
27
+ | Option | Type | Default | Description |
28
+ |--------|------|---------|-------------|
29
+ | `allowed_roots` | `list[Path]` | `[Path.cwd()]` | Filesystem roots the engine can access. PathJail blocks all others. |
30
+ | `ledger_path` | `Path` | `./sovereign.worm` | Binary WORM ledger file for cryptographic audit trail. |
31
+ | `continuity_dir` | `Path` | `~/.sovereign/continuity` | Directory for all four continuity backends. Auto-created. |
32
+ | `max_steps` | `int` | `15` | Max reasoning steps in ReActAgent loop before timeout. |
33
+ | `enable_shadow` | `bool` | `True` | Enable ShadowAgent for non-blocking observation. |
34
+ | `enable_ipc` | `bool` | `True` | Enable native IPC multiplexer (O(1) tool dispatch). |
35
+ | `agent_id` | `str` | `"sovereign_main"` | Unique agent identifier (for multi-agent coordination). |
36
+
37
+ ## Environment Variables
38
+
39
+ Override config via environment variables (prefixed with `SOVEREIGN_`):
40
+
41
+ ```bash
42
+ export SOVEREIGN_ALLOWED_ROOTS="/home/user/projects:/tmp"
43
+ export SOVEREIGN_MAX_STEPS=25
44
+ export SOVEREIGN_ENABLE_SHADOW=false
45
+ export SOVEREIGN_AGENT_ID="batch_processor_1"
46
+
47
+ python your_script.py
48
+ ```
49
+
50
+ Supported environment variables:
51
+
52
+ | Variable | Type | Maps To |
53
+ |----------|------|---------|
54
+ | `SOVEREIGN_ALLOWED_ROOTS` | CSV paths | `allowed_roots` (split on `:` or `;`) |
55
+ | `SOVEREIGN_LEDGER_PATH` | path | `ledger_path` |
56
+ | `SOVEREIGN_CONTINUITY_DIR` | path | `continuity_dir` |
57
+ | `SOVEREIGN_MAX_STEPS` | int | `max_steps` |
58
+ | `SOVEREIGN_ENABLE_SHADOW` | bool | `enable_shadow` (true/false/1/0) |
59
+ | `SOVEREIGN_ENABLE_IPC` | bool | `enable_ipc` |
60
+ | `SOVEREIGN_AGENT_ID` | str | `agent_id` |
61
+
62
+ Example:
63
+
64
+ ```python
65
+ import os
66
+ from src.sovereign import SovereignEngine
67
+
68
+ # Environment variables take precedence
69
+ if "SOVEREIGN_MAX_STEPS" in os.environ:
70
+ max_steps = int(os.environ["SOVEREIGN_MAX_STEPS"])
71
+ else:
72
+ max_steps = 15
73
+ ```
74
+
75
+ ## ReActAgent Config
76
+
77
+ The ReAct reasoning agent has its own config:
78
+
79
+ ```python
80
+ from src.agents.react import ReActConfig, ReActAgent
81
+
82
+ react_config = ReActConfig(
83
+ max_steps=20,
84
+ timeout_ms=60000,
85
+ model_provider="bedrock", # bedrock, openrouter, ollama, anthropic
86
+ temperature=0.7,
87
+ top_p=0.9,
88
+ )
89
+
90
+ agent = ReActAgent(
91
+ agent_id="code_agent",
92
+ config=react_config,
93
+ registry=tool_registry,
94
+ ledger=worm_ledger,
95
+ )
96
+ ```
97
+
98
+ | Option | Type | Default | Description |
99
+ |--------|------|---------|-------------|
100
+ | `max_steps` | `int` | `15` | Max thinking/action loops. |
101
+ | `timeout_ms` | `int` | `30000` | Total execution timeout. |
102
+ | `model_provider` | `str` | `"bedrock"` | LLM backend: bedrock, openrouter, ollama, anthropic. |
103
+ | `temperature` | `float` | `0.7` | Sampling temperature (0.0-1.0). |
104
+ | `top_p` | `float` | `0.9` | Nucleus sampling parameter. |
105
+
106
+ ## Continuity Directory Structure
107
+
108
+ The engine uses 4 paradigms for state recovery. Structure:
109
+
110
+ ```
111
+ ~/.sovereign/continuity/
112
+ ├── env_state.bin # Paradigm 1: Env bitmask (for hot-restart)
113
+ ├── seed.bin # Paradigm 2: Seed for deterministic replay
114
+ ├── agent_id_seed.bin # Paradigm 2 per-agent seed
115
+ ├── inode/
116
+ │ ├── flags.db # Paradigm 3: File handles mapped to boolean gates
117
+ │ ├── agent_id.lock
118
+ │ └── agent_id.state
119
+ ├── shm/
120
+ │ └── block_0.bin # Paradigm 4: Shared memory block (4KB)
121
+ └── checkpoints/
122
+ ├── checkpoint_001.ckpt # Full state snapshot with signature
123
+ ├── checkpoint_002.ckpt
124
+ └── manifest.json
125
+ ```
126
+
127
+ **Paradigm 1: Env State** — 64-bit bitmask in `os.environ["SOVEREIGN_STATE"]`:
128
+ - Fastest (RAM)
129
+ - Lost on process exit
130
+ - Use for: hot-restart via `os.execv()`
131
+
132
+ **Paradigm 2: Seed Chain** — Blake2b seed derivation:
133
+ - Deterministic replay
134
+ - Store operation log in binary
135
+ - Use for: replay testing, timeline forking
136
+
137
+ **Paradigm 3: Inode State** — Zero-byte files as gates:
138
+ - Kernel-durable
139
+ - Fast stat() reads
140
+ - Use for: boolean flags, pause/resume
141
+
142
+ **Paradigm 4: Shared Memory** — ctypes mmap block:
143
+ - Cross-process coordination
144
+ - 4KB block
145
+ - Use for: multi-agent sync, realtime state
146
+
147
+ ## PathJail Configuration
148
+
149
+ Restrict filesystem access to approved roots:
150
+
151
+ ```python
152
+ from src.core.path_jail import PathJail
153
+ from pathlib import Path
154
+
155
+ jail = PathJail(roots=[
156
+ Path("/home/user/projects"),
157
+ Path("/tmp/scratch"),
158
+ Path.home() / "Downloads"
159
+ ])
160
+
161
+ # This is OK
162
+ jail.check("/home/user/projects/code.py") # ✓
163
+
164
+ # This is BLOCKED
165
+ jail.check("/etc/passwd") # ✗ PathJailError
166
+ jail.check("/root/.ssh/id_rsa") # ✗ PathJailError
167
+ ```
168
+
169
+ Pass to engine:
170
+
171
+ ```python
172
+ config = EngineConfig(
173
+ allowed_roots=[
174
+ Path("/home/user/projects"),
175
+ Path("/tmp/scratch"),
176
+ ]
177
+ )
178
+ engine = SovereignEngine(config)
179
+ ```
180
+
181
+ ## Model Provider Configuration
182
+
183
+ Choose which LLM backend to use. Defaults to **AWS Bedrock** (all models via unified API).
184
+
185
+ ### Bedrock (Recommended)
186
+
187
+ ```python
188
+ from src.sovereign import EngineConfig
189
+ from src.runtime.providers.bedrock import BedrockProvider
190
+
191
+ config = EngineConfig()
192
+ provider = BedrockProvider(
193
+ region="us-west-2",
194
+ model="anthropic.claude-3-opus-20240229-v1:0",
195
+ )
196
+
197
+ # Engine automatically uses it
198
+ engine = SovereignEngine(config)
199
+ ```
200
+
201
+ Requires `~/.aws/credentials`:
202
+ ```
203
+ [default]
204
+ aws_access_key_id = YOUR_KEY
205
+ aws_secret_access_key = YOUR_SECRET
206
+ region = us-west-2
207
+ ```
208
+
209
+ ### OpenRouter (Open-source + proprietary models)
210
+
211
+ ```python
212
+ from src.runtime.providers.openrouter import OpenRouterProvider
213
+
214
+ provider = OpenRouterProvider(api_key="sk-or-...")
215
+ # Uses OpenRouter API for Mistral, Llama, Qwen, etc.
216
+ ```
217
+
218
+ Requires `OPENROUTER_API_KEY` environment variable.
219
+
220
+ ### Ollama (Local models)
221
+
222
+ ```python
223
+ from src.runtime.providers.ollama import OllamaProvider
224
+
225
+ provider = OllamaProvider(base_url="http://localhost:11434")
226
+ # Connect to local Ollama instance (ollama serve)
227
+ ```
228
+
229
+ Run locally first:
230
+ ```bash
231
+ ollama run mistral
232
+ # or
233
+ ollama run llama2
234
+ ```
235
+
236
+ ### Anthropic API (Direct)
237
+
238
+ ```python
239
+ from src.runtime.providers.anthropic import AnthropicProvider
240
+
241
+ provider = AnthropicProvider(api_key="sk-ant-...")
242
+ ```
243
+
244
+ ## Example Configurations
245
+
246
+ ### Development Mode
247
+
248
+ Minimal logging, fast iteration:
249
+
250
+ ```python
251
+ from src.sovereign import EngineConfig
252
+ from pathlib import Path
253
+ import logging
254
+
255
+ logging.basicConfig(level=logging.DEBUG)
256
+
257
+ config = EngineConfig(
258
+ allowed_roots=[Path.cwd()],
259
+ ledger_path=Path("/tmp/dev.worm"),
260
+ continuity_dir=Path("/tmp/continuity"),
261
+ max_steps=10, # Fast iteration
262
+ enable_shadow=False,
263
+ )
264
+ ```
265
+
266
+ ### Production Mode
267
+
268
+ Full logging, durability, multi-agent:
269
+
270
+ ```python
271
+ config = EngineConfig(
272
+ allowed_roots=[
273
+ Path("/var/app/data"),
274
+ Path("/var/app/cache"),
275
+ ],
276
+ ledger_path=Path("/var/lib/sovereign/ledger.worm"),
277
+ continuity_dir=Path("/var/lib/sovereign/continuity"),
278
+ max_steps=25, # More thinking
279
+ enable_shadow=True, # Async observation
280
+ enable_ipc=True, # Native IPC
281
+ agent_id="prod_reactor_1",
282
+ )
283
+ ```
284
+
285
+ With Bedrock:
286
+
287
+ ```python
288
+ from src.runtime.providers.bedrock import BedrockProvider
289
+
290
+ provider = BedrockProvider(
291
+ region="us-east-1",
292
+ model="anthropic.claude-3-sonnet-20240229-v1:0",
293
+ timeout_ms=120000,
294
+ )
295
+ ```
296
+
297
+ ### CI Testing Mode
298
+
299
+ Fast, deterministic, no external calls:
300
+
301
+ ```python
302
+ config = EngineConfig(
303
+ allowed_roots=[Path("/tmp/ci_test")],
304
+ ledger_path=Path("/tmp/ci.worm"),
305
+ continuity_dir=Path("/tmp/ci_continuity"),
306
+ max_steps=5,
307
+ enable_shadow=False,
308
+ enable_ipc=False, # Use pure Python
309
+ agent_id=f"ci_test_{os.getenv('CI_BUILD_ID')}",
310
+ )
311
+ ```
312
+
313
+ Use mock provider for testing:
314
+
315
+ ```python
316
+ from src.runtime.providers.multi import MultiProvider
317
+
318
+ # Fallback chain: try each in order
319
+ provider = MultiProvider([
320
+ MockProvider(), # Return fixed responses
321
+ OllamaProvider(), # Fall back to local if available
322
+ ])
323
+ ```
324
+
325
+ ## Logging Configuration
326
+
327
+ Control verbosity via Python logging:
328
+
329
+ ```python
330
+ import logging
331
+
332
+ # Engine logs
333
+ logging.getLogger("sovereign.engine").setLevel(logging.INFO)
334
+
335
+ # Routing pipeline
336
+ logging.getLogger("sovereign.routing").setLevel(logging.DEBUG)
337
+
338
+ # Tool registry
339
+ logging.getLogger("sovereign.tools").setLevel(logging.WARNING)
340
+
341
+ # Everything
342
+ logging.basicConfig(
343
+ level=logging.DEBUG,
344
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
345
+ )
346
+ ```
347
+
348
+ ## Dynamic Configuration Updates
349
+
350
+ Update config without restarting:
351
+
352
+ ```python
353
+ engine = SovereignEngine(config)
354
+
355
+ # Update max_steps
356
+ engine.config.max_steps = 25
357
+
358
+ # Restart agent with new config
359
+ from src.agents.react import ReActConfig
360
+ engine.agent.config.max_steps = 25
361
+
362
+ # Add a new allowed root
363
+ engine.config.allowed_roots.append(Path("/new/path"))
364
+ engine.path_jail = PathJail(roots=engine.config.allowed_roots)
365
+ ```
366
+
367
+ ## Configuration Validation
368
+
369
+ Validate config before starting engine:
370
+
371
+ ```python
372
+ from src.sovereign import EngineConfig
373
+ from pathlib import Path
374
+
375
+ config = EngineConfig(
376
+ allowed_roots=[Path("/tmp/test")],
377
+ )
378
+
379
+ # Check roots exist
380
+ for root in config.allowed_roots:
381
+ if not root.exists():
382
+ root.mkdir(parents=True, exist_ok=True)
383
+
384
+ # Check continuity dir writable
385
+ try:
386
+ config.continuity_dir.mkdir(parents=True, exist_ok=True)
387
+ (config.continuity_dir / ".test").touch()
388
+ (config.continuity_dir / ".test").unlink()
389
+ except PermissionError:
390
+ raise RuntimeError(f"Cannot write to {config.continuity_dir}")
391
+ ```
docs/GETTING_STARTED.md ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Getting Started with Sovereign Engine
2
+
3
+ Welcome! This guide will get you running your first agent task in under 10 minutes.
4
+
5
+ ## Prerequisites
6
+
7
+ - **Python 3.11 or higher** — Check with: `python --version`
8
+ - **No external dependencies** — Everything uses Python stdlib (struct, hashlib, asyncio, etc.)
9
+ - Optional: NASM (for native assembly compilation)
10
+ - Optional: C compiler (for IPC core dispatcher)
11
+
12
+ ## Installation
13
+
14
+ ### Option 1: From Source
15
+
16
+ ```bash
17
+ git clone https://github.com/SNAPKITTYWEST/sovereign-reverse.git
18
+ cd sovereign-reverse/engine
19
+ python -m pip install -e .
20
+ ```
21
+
22
+ ### Option 2: Install Locally
23
+
24
+ ```bash
25
+ cd /path/to/sovereign-reverse/engine
26
+ python -m pip install -e .
27
+ ```
28
+
29
+ ### Verify Installation
30
+
31
+ ```bash
32
+ python -c "from src.sovereign import SovereignEngine; print('OK')"
33
+ ```
34
+
35
+ ## First Run: Hello World
36
+
37
+ Create a file `hello.py`:
38
+
39
+ ```python
40
+ import asyncio
41
+ from pathlib import Path
42
+ from src.sovereign import SovereignEngine, EngineConfig
43
+
44
+ async def main():
45
+ # Create engine with default config
46
+ config = EngineConfig(
47
+ allowed_roots=[Path.cwd()],
48
+ continuity_dir=Path.home() / ".sovereign" / "continuity"
49
+ )
50
+ engine = SovereignEngine(config)
51
+
52
+ # Run a simple task
53
+ result = await engine.run("write a Python function to calculate fibonacci(5)")
54
+ print("\n" + "="*60)
55
+ print("RESULT:")
56
+ print("="*60)
57
+ print(result)
58
+
59
+ # Clean up
60
+ engine.shutdown()
61
+
62
+ if __name__ == "__main__":
63
+ asyncio.run(main())
64
+ ```
65
+
66
+ Run it:
67
+
68
+ ```bash
69
+ python hello.py
70
+ ```
71
+
72
+ You should see output like:
73
+
74
+ ```
75
+ 2024-08-06 10:15:23,456 - sovereign.engine - INFO - SovereignEngine initialized
76
+ 2024-08-06 10:15:23,478 - sovereign.engine - INFO - Starting task: write a Python function to calculate fibonacci(5)
77
+ ...
78
+ ============================================================
79
+ RESULT:
80
+ ============================================================
81
+ def fibonacci(n):
82
+ if n <= 1:
83
+ return n
84
+ return fibonacci(n-1) + fibonacci(n-2)
85
+
86
+ print(fibonacci(5)) # Output: 5
87
+ ```
88
+
89
+ ## Understanding the Output
90
+
91
+ ### WORM Seal
92
+
93
+ Every task execution is sealed in a **Write-Once Read-Many (WORM)** cryptographic ledger:
94
+
95
+ ```
96
+ sovereign.worm # Binary append-only file (no text, no injection surface)
97
+ └─ 152-byte header # Magic + version + timestamps + Blake2b hash + Ed25519 sig
98
+ └─ payload # Your task result (binary encoded)
99
+ └─ metadata # Execution context (binary encoded)
100
+ ```
101
+
102
+ Read the ledger:
103
+
104
+ ```python
105
+ from src.core.storage import WORMFile
106
+ from pathlib import Path
107
+
108
+ wf = WORMFile(Path("sovereign.worm"))
109
+ for record in wf.scan():
110
+ print(f"Event: {record.event_type}")
111
+ print(f"Timestamp: {record.timestamp_s}")
112
+ print(f"Hash: {record.content_hash.hex()}")
113
+ ```
114
+
115
+ ### Routing Weights
116
+
117
+ The engine routes tasks to multiple **experts** (specialized agents). Output shows which experts fired and their contribution:
118
+
119
+ ```
120
+ Routing Decision:
121
+ code_generation: 0.65 # 65% contribution
122
+ error_checking: 0.28 # 28% contribution
123
+ documentation: 0.07 # 7% contribution
124
+ ```
125
+
126
+ This is driven by **Jordan algebra**, not softmax:
127
+ - Non-associative grouping — expert topology matters
128
+ - Fixed-point convergence — stable routing attractors
129
+ - Spectral decomposition — provably unique expert selection
130
+
131
+ ### Continuity Directory
132
+
133
+ Agent state survives restarts across 4 paradigms:
134
+
135
+ ```
136
+ ~/.sovereign/continuity/
137
+ └─ env_state.bin # Paradigm 1: 64-bit env bitmask (hot-restart)
138
+ └─ seed.bin # Paradigm 2: Blake2b seed for deterministic replay
139
+ └─ inode/
140
+ │ └─ agent_id.state # Paradigm 3: Zero-byte files as boolean gates
141
+ │ └─ agent_id.lock
142
+ └─ shm_block.bin # Paradigm 4: Shared memory (cross-process realtime)
143
+ └─ checkpoints/
144
+ └─ checkpoint_1.ckpt # Full binary snapshot with signature
145
+ └─ checkpoint_2.ckpt
146
+ ```
147
+
148
+ ## Common First-Time Issues
149
+
150
+ ### Issue: "ModuleNotFoundError: No module named 'src.sovereign'"
151
+
152
+ **Fix:** Make sure you're in the engine directory and have run `pip install -e .`:
153
+
154
+ ```bash
155
+ cd /path/to/sovereign-reverse/engine
156
+ python -m pip install -e .
157
+ python hello.py
158
+ ```
159
+
160
+ ### Issue: "PermissionError: Cannot create continuity directory"
161
+
162
+ **Fix:** The engine needs to write to `~/.sovereign/continuity`. Make sure it exists and is writable:
163
+
164
+ ```bash
165
+ mkdir -p ~/.sovereign/continuity
166
+ chmod 700 ~/.sovereign/continuity
167
+ ```
168
+
169
+ Or use a custom path:
170
+
171
+ ```python
172
+ config = EngineConfig(
173
+ continuity_dir=Path("/tmp/my_continuity")
174
+ )
175
+ ```
176
+
177
+ ### Issue: "ValueError: no signature in WORM record"
178
+
179
+ **Fix:** This is expected if no signing key is configured. The engine defaults to zero-signature (all bytes 0). To use real Ed25519 signing:
180
+
181
+ ```python
182
+ from src.core.crypto import generate_signing_key
183
+
184
+ key = generate_signing_key()
185
+ engine = SovereignEngine(config)
186
+ # engine will use key internally
187
+ ```
188
+
189
+ ### Issue: "Timeout: task exceeded 30 seconds"
190
+
191
+ **Fix:** Increase the max step count or timeout in config:
192
+
193
+ ```python
194
+ from src.agents.react import ReActConfig
195
+
196
+ config = EngineConfig(
197
+ max_steps=25 # More thinking steps
198
+ )
199
+ engine = SovereignEngine(config)
200
+ ```
201
+
202
+ Or in individual agent config:
203
+
204
+ ```python
205
+ from src.agents.react import ReActAgent, ReActConfig
206
+
207
+ react_config = ReActConfig(max_steps=25, timeout_ms=60000)
208
+ ```
209
+
210
+ ### Issue: "PathJail: denied /path/to/file"
211
+
212
+ **Fix:** The engine restricts filesystem access to `allowed_roots`. Add your path:
213
+
214
+ ```python
215
+ config = EngineConfig(
216
+ allowed_roots=[
217
+ Path("/home/user/projects"),
218
+ Path("/tmp/scratch")
219
+ ]
220
+ )
221
+ ```
222
+
223
+ ## Next Steps
224
+
225
+ - **[CONFIGURATION.md](CONFIGURATION.md)** — All EngineConfig options with defaults
226
+ - **[ROUTING.md](ROUTING.md)** — How the 11-stage pipeline works
227
+ - **[TOOLS.md](TOOLS.md)** — Complete tool inventory (34 tools)
228
+ - **[CONTINUITY.md](CONTINUITY.md)** — State recovery and replay
229
+ - **[SECURITY.md](SECURITY.md)** — PathJail, SSRFGuard, WORM verification
230
+
231
+ ## Production Checklist
232
+
233
+ Before shipping to production:
234
+
235
+ - [ ] Set up Ed25519 signing key (see [SECURITY.md](SECURITY.md))
236
+ - [ ] Configure `allowed_roots` to restrict filesystem access
237
+ - [ ] Enable WORM ledger integrity checks (script in [SECURITY.md](SECURITY.md))
238
+ - [ ] Set up continuity directory with proper permissions
239
+ - [ ] Test task recovery (kill agent mid-task, restart)
240
+ - [ ] Configure rate limits on destructive tools (see [TOOLS.md](TOOLS.md))
241
+ - [ ] Enable shadow agent for async observation: `enable_shadow=True`
242
+ - [ ] Set appropriate model provider in config (Bedrock/OpenRouter/Ollama)
ide/CMakeLists.txt ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 3.24)
2
+ project(sovereign-ide C CXX)
3
+
4
+ set(CMAKE_C_STANDARD 17)
5
+ set(CMAKE_C_STANDARD_REQUIRED ON)
6
+ set(CMAKE_CXX_STANDARD 17)
7
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
8
+
9
+ if(NOT WIN32)
10
+ message(FATAL_ERROR "Sovereign IDE targets Windows only (Win32 + Direct2D)")
11
+ endif()
12
+
13
+ add_compile_definitions(_WIN32_WINNT=0x0A00 NTDDI_VERSION=0x0A000000)
14
+
15
+ if(MSVC)
16
+ add_compile_options(/W4 /WX /utf-8)
17
+ else()
18
+ add_compile_options(-Wall -Wextra -Werror -Wno-missing-field-initializers)
19
+ endif()
20
+
21
+ # --- Core library (pure C, no GUI deps) ---
22
+ add_library(sov_core STATIC
23
+ native/core/arena.c
24
+ native/core/strings.c
25
+ native/core/events.c
26
+ native/editor/buffer.c
27
+ native/editor/document.c
28
+ native/chat/protocol.c
29
+ native/fcl/evaluator.c
30
+ native/git/repository.c
31
+ native/build/cmake_runner.c
32
+ native/lsp/client.c
33
+ )
34
+ target_include_directories(sov_core PUBLIC native)
35
+
36
+ # --- Editor graphics (C++, Direct2D + DirectWrite) ---
37
+ add_library(sov_editor STATIC
38
+ native/graphics/d2d_renderer.cpp
39
+ native/editor/editor_view.cpp
40
+ )
41
+ target_include_directories(sov_editor PUBLIC native)
42
+ target_link_libraries(sov_editor PUBLIC sov_core d2d1 dwrite dxgi ole32)
43
+
44
+ # --- UI widgets (C++, Direct2D + DirectWrite) ---
45
+ add_library(sov_ui STATIC
46
+ native/ui/layout.c
47
+ native/ui/project_tree.cpp
48
+ native/ui/status_bar.cpp
49
+ native/ui/output_panel.cpp
50
+ native/terminal/conpty.c
51
+ native/terminal/terminal_view.cpp
52
+ )
53
+ target_include_directories(sov_ui PUBLIC native)
54
+ target_link_libraries(sov_ui PUBLIC sov_core sov_editor d2d1 dwrite ole32)
55
+
56
+ # --- Platform layer (C, Win32) ---
57
+ add_library(sov_platform STATIC
58
+ native/platform/windows/application.c
59
+ native/platform/windows/shell.c
60
+ native/chat/pipe_client.c
61
+ )
62
+ target_include_directories(sov_platform PUBLIC native)
63
+ target_link_libraries(sov_platform PUBLIC sov_core sov_editor sov_ui user32 gdi32 shcore comdlg32 shell32)
64
+
65
+ # --- Main executable ---
66
+ add_executable(sovereign-ide WIN32 native/main.c)
67
+ target_link_libraries(sovereign-ide PRIVATE sov_core sov_platform sov_editor sov_ui)
68
+ if(MINGW)
69
+ target_link_options(sovereign-ide PRIVATE -municode -static-libgcc -static-libstdc++ -static)
70
+ endif()
71
+
72
+ set_target_properties(sovereign-ide PROPERTIES
73
+ OUTPUT_NAME "sovereign-ide"
74
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
75
+ )
76
+
77
+ # --- Tests ---
78
+ enable_testing()
79
+
80
+ add_executable(test_core
81
+ tests/test_arena.c
82
+ tests/test_strings.c
83
+ tests/test_events.c
84
+ tests/test_buffer.c
85
+ tests/test_protocol.c
86
+ tests/test_fcl.c
87
+ tests/test_main.c
88
+ )
89
+ target_link_libraries(test_core PRIVATE sov_core)
90
+ target_include_directories(test_core PRIVATE native)
91
+
92
+ add_test(NAME core_tests COMMAND test_core)
ide/native/bridge/bridge.h ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Bridge Client — C → Python Engine
3
+ * Sovereign IDE Bridge Layer
4
+ *
5
+ * Communicates with Python sovereign-engine via stdio or HTTP.
6
+ */
7
+
8
+ #ifndef BRIDGE_H
9
+ #define BRIDGE_H
10
+
11
+ #include <stddef.h>
12
+ #include <stdbool.h>
13
+
14
+ #ifdef __cplusplus
15
+ extern "C" {
16
+ #endif
17
+
18
+ /* Bridge transport types */
19
+ typedef enum {
20
+ BRIDGE_TRANSPORT_STDIO,
21
+ BRIDGE_TRANSPORT_HTTP
22
+ } BridgeTransport;
23
+
24
+ /* Bridge result codes */
25
+ typedef enum {
26
+ BRIDGE_OK = 0,
27
+ BRIDGE_ERROR_INIT = -1,
28
+ BRIDGE_ERROR_SEND = -2,
29
+ BRIDGE_ERROR_RECV = -3,
30
+ BRIDGE_ERROR_PARSE = -4,
31
+ BRIDGE_ERROR_TIMEOUT = -5
32
+ } BridgeResult;
33
+
34
+ /* Bridge handle (opaque) */
35
+ typedef struct Bridge Bridge;
36
+
37
+ /* Bridge configuration */
38
+ typedef struct {
39
+ BridgeTransport transport;
40
+
41
+ /* HTTP config */
42
+ const char* http_host;
43
+ unsigned short http_port;
44
+
45
+ /* Stdio config */
46
+ const char* python_executable;
47
+ const char* bridge_script;
48
+
49
+ /* Timeouts (milliseconds) */
50
+ unsigned int request_timeout;
51
+ } BridgeConfig;
52
+
53
+ /* Response callback */
54
+ typedef void (*BridgeCallback)(const char* response_json, void* user_data);
55
+
56
+ /*
57
+ * Initialize bridge
58
+ * Returns bridge handle on success, NULL on failure
59
+ */
60
+ Bridge* bridge_init(const BridgeConfig* config);
61
+
62
+ /*
63
+ * Shutdown bridge
64
+ */
65
+ void bridge_shutdown(Bridge* bridge);
66
+
67
+ /*
68
+ * Check if bridge is ready
69
+ */
70
+ bool bridge_is_ready(Bridge* bridge);
71
+
72
+ /*
73
+ * Run agent task (blocking)
74
+ * Returns JSON response string (caller must free)
75
+ */
76
+ char* bridge_agent_run(Bridge* bridge, const char* task);
77
+
78
+ /*
79
+ * Run agent task (async)
80
+ */
81
+ BridgeResult bridge_agent_run_async(
82
+ Bridge* bridge,
83
+ const char* task,
84
+ BridgeCallback callback,
85
+ void* user_data
86
+ );
87
+
88
+ /*
89
+ * Execute tool (blocking)
90
+ */
91
+ char* bridge_tool_execute(
92
+ Bridge* bridge,
93
+ const char* tool_name,
94
+ const char* args_json
95
+ );
96
+
97
+ /*
98
+ * List available tools (blocking)
99
+ */
100
+ char* bridge_tools_list(Bridge* bridge);
101
+
102
+ /*
103
+ * Send chat message (blocking)
104
+ */
105
+ char* bridge_chat_message(Bridge* bridge, const char* message);
106
+
107
+ /*
108
+ * Health check (blocking)
109
+ */
110
+ bool bridge_health_check(Bridge* bridge);
111
+
112
+ /*
113
+ * Get last error message
114
+ */
115
+ const char* bridge_get_error(Bridge* bridge);
116
+
117
+ /*
118
+ * Set API key for provider
119
+ * provider: "openrouter" or "ollama"
120
+ * Returns JSON response (caller must free)
121
+ */
122
+ char* bridge_set_key(Bridge* bridge, const char* provider, const char* api_key);
123
+
124
+ /*
125
+ * Get API key status
126
+ * Returns JSON with all provider statuses (caller must free)
127
+ */
128
+ char* bridge_get_key_status(Bridge* bridge);
129
+
130
+ /*
131
+ * Delete API key for provider
132
+ * Returns JSON response (caller must free)
133
+ */
134
+ char* bridge_delete_key(Bridge* bridge, const char* provider);
135
+
136
+ #ifdef __cplusplus
137
+ }
138
+ #endif
139
+
140
+ #endif /* BRIDGE_H */
ide/native/bridge/bridge_http.c ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Bridge HTTP Client Implementation
3
+ * Uses WinHTTP for HTTP communication with Python engine
4
+ */
5
+
6
+ #include "bridge.h"
7
+ #include <windows.h>
8
+ #include <winhttp.h>
9
+ #include <stdio.h>
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+
13
+ #pragma comment(lib, "winhttp.lib")
14
+
15
+ /* Bridge internal structure */
16
+ struct Bridge {
17
+ BridgeConfig config;
18
+ HINTERNET hSession;
19
+ HINTERNET hConnect;
20
+ char error_msg[512];
21
+ bool ready;
22
+ };
23
+
24
+ /* Helper: validate input length for JSON */
25
+ static bool validate_input_length(Bridge* bridge, const char* input, const char* field_name) {
26
+ size_t len = strlen(input);
27
+ if (len > 4000) { /* Max input: 4KB minus JSON overhead */
28
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
29
+ "%s too long: %lu chars (max 4000)", field_name, (unsigned long)len);
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+
35
+ /* Helper: escape JSON special characters */
36
+ static char* json_escape(const char* str) {
37
+ if (!str) return NULL;
38
+
39
+ size_t len = strlen(str);
40
+ /* Worst case: every char needs escaping (doubled length) */
41
+ char* escaped = (char*)malloc(len * 2 + 1);
42
+ if (!escaped) return NULL;
43
+
44
+ size_t j = 0;
45
+ for (size_t i = 0; i < len; i++) {
46
+ switch (str[i]) {
47
+ case '"':
48
+ case '\\':
49
+ escaped[j++] = '\\';
50
+ escaped[j++] = str[i];
51
+ break;
52
+ case '\n':
53
+ escaped[j++] = '\\';
54
+ escaped[j++] = 'n';
55
+ break;
56
+ case '\r':
57
+ escaped[j++] = '\\';
58
+ escaped[j++] = 'r';
59
+ break;
60
+ case '\t':
61
+ escaped[j++] = '\\';
62
+ escaped[j++] = 't';
63
+ break;
64
+ default:
65
+ escaped[j++] = str[i];
66
+ break;
67
+ }
68
+ }
69
+ escaped[j] = '\0';
70
+ return escaped;
71
+ }
72
+
73
+ /* Helper: HTTP POST request */
74
+ static char* http_post(Bridge* bridge, const char* path, const char* body) {
75
+ if (!bridge || !bridge->hConnect) {
76
+ return NULL;
77
+ }
78
+
79
+ /* Convert path to wide string */
80
+ wchar_t wide_path[256];
81
+ MultiByteToWideChar(CP_UTF8, 0, path ? path : "/", -1, wide_path, 256);
82
+
83
+ /* Open request */
84
+ HINTERNET hRequest = WinHttpOpenRequest(
85
+ bridge->hConnect,
86
+ L"POST",
87
+ wide_path,
88
+ NULL,
89
+ WINHTTP_NO_REFERER,
90
+ WINHTTP_DEFAULT_ACCEPT_TYPES,
91
+ 0
92
+ );
93
+
94
+ if (!hRequest) {
95
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
96
+ "Failed to open HTTP request");
97
+ return NULL;
98
+ }
99
+
100
+ /* Set headers */
101
+ LPCWSTR headers = L"Content-Type: application/json\r\n";
102
+ WinHttpAddRequestHeaders(hRequest, headers, -1, WINHTTP_ADDREQ_FLAG_ADD);
103
+
104
+ /* Send request */
105
+ DWORD body_len = (DWORD)strlen(body);
106
+ BOOL result = WinHttpSendRequest(
107
+ hRequest,
108
+ WINHTTP_NO_ADDITIONAL_HEADERS,
109
+ 0,
110
+ (LPVOID)body,
111
+ body_len,
112
+ body_len,
113
+ 0
114
+ );
115
+
116
+ if (!result) {
117
+ WinHttpCloseHandle(hRequest);
118
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
119
+ "Failed to send HTTP request");
120
+ return NULL;
121
+ }
122
+
123
+ /* Receive response */
124
+ result = WinHttpReceiveResponse(hRequest, NULL);
125
+ if (!result) {
126
+ WinHttpCloseHandle(hRequest);
127
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
128
+ "Failed to receive HTTP response");
129
+ return NULL;
130
+ }
131
+
132
+ /* Read response body */
133
+ DWORD bytes_available = 0;
134
+ DWORD bytes_read = 0;
135
+ char* response = NULL;
136
+ size_t response_len = 0;
137
+
138
+ do {
139
+ bytes_available = 0;
140
+ if (!WinHttpQueryDataAvailable(hRequest, &bytes_available)) {
141
+ break;
142
+ }
143
+
144
+ if (bytes_available == 0) {
145
+ break;
146
+ }
147
+
148
+ /* Allocate buffer */
149
+ char* buffer = (char*)malloc(bytes_available + 1);
150
+ if (!buffer) break;
151
+
152
+ /* Read data */
153
+ if (WinHttpReadData(hRequest, buffer, bytes_available, &bytes_read)) {
154
+ /* Append to response */
155
+ char* new_response = (char*)realloc(response, response_len + bytes_read + 1);
156
+ if (new_response) {
157
+ response = new_response;
158
+ memcpy(response + response_len, buffer, bytes_read);
159
+ response_len += bytes_read;
160
+ response[response_len] = '\0';
161
+ } else {
162
+ /* realloc failed - clean up */
163
+ free(response);
164
+ free(buffer);
165
+ WinHttpCloseHandle(hRequest);
166
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
167
+ "Out of memory during HTTP response");
168
+ return NULL;
169
+ }
170
+ }
171
+
172
+ free(buffer);
173
+ } while (bytes_available > 0);
174
+
175
+ WinHttpCloseHandle(hRequest);
176
+ return response;
177
+ }
178
+
179
+ /* Helper: HTTP GET request */
180
+ static char* http_get(Bridge* bridge, const char* path) {
181
+ if (!bridge || !bridge->hConnect) {
182
+ return NULL;
183
+ }
184
+
185
+ /* Convert path to wide string */
186
+ wchar_t wide_path[256];
187
+ MultiByteToWideChar(CP_UTF8, 0, path ? path : "/", -1, wide_path, 256);
188
+
189
+ /* Open request */
190
+ HINTERNET hRequest = WinHttpOpenRequest(
191
+ bridge->hConnect,
192
+ L"GET",
193
+ wide_path,
194
+ NULL,
195
+ WINHTTP_NO_REFERER,
196
+ WINHTTP_DEFAULT_ACCEPT_TYPES,
197
+ 0
198
+ );
199
+
200
+ if (!hRequest) {
201
+ return NULL;
202
+ }
203
+
204
+ /* Send request */
205
+ BOOL result = WinHttpSendRequest(
206
+ hRequest,
207
+ WINHTTP_NO_ADDITIONAL_HEADERS,
208
+ 0,
209
+ WINHTTP_NO_REQUEST_DATA,
210
+ 0,
211
+ 0,
212
+ 0
213
+ );
214
+
215
+ if (!result) {
216
+ WinHttpCloseHandle(hRequest);
217
+ return NULL;
218
+ }
219
+
220
+ /* Receive response */
221
+ result = WinHttpReceiveResponse(hRequest, NULL);
222
+ if (!result) {
223
+ WinHttpCloseHandle(hRequest);
224
+ return NULL;
225
+ }
226
+
227
+ /* Read response (same as POST) */
228
+ DWORD bytes_available = 0;
229
+ DWORD bytes_read = 0;
230
+ char* response = NULL;
231
+ size_t response_len = 0;
232
+
233
+ do {
234
+ bytes_available = 0;
235
+ if (!WinHttpQueryDataAvailable(hRequest, &bytes_available)) {
236
+ break;
237
+ }
238
+
239
+ if (bytes_available == 0) {
240
+ break;
241
+ }
242
+
243
+ char* buffer = (char*)malloc(bytes_available + 1);
244
+ if (!buffer) {
245
+ free(response);
246
+ break;
247
+ }
248
+
249
+ if (WinHttpReadData(hRequest, buffer, bytes_available, &bytes_read)) {
250
+ char* new_response = (char*)realloc(response, response_len + bytes_read + 1);
251
+ if (new_response) {
252
+ response = new_response;
253
+ memcpy(response + response_len, buffer, bytes_read);
254
+ response_len += bytes_read;
255
+ response[response_len] = '\0';
256
+ } else {
257
+ /* realloc failed - clean up */
258
+ free(response);
259
+ free(buffer);
260
+ WinHttpCloseHandle(hRequest);
261
+ return NULL;
262
+ }
263
+ }
264
+
265
+ free(buffer);
266
+ } while (bytes_available > 0);
267
+
268
+ WinHttpCloseHandle(hRequest);
269
+ return response;
270
+ }
271
+
272
+ /* Public API */
273
+
274
+ Bridge* bridge_init(const BridgeConfig* config) {
275
+ if (!config) return NULL;
276
+
277
+ Bridge* bridge = (Bridge*)calloc(1, sizeof(Bridge));
278
+ if (!bridge) return NULL;
279
+
280
+ /* Copy config */
281
+ memcpy(&bridge->config, config, sizeof(BridgeConfig));
282
+
283
+ if (config->transport == BRIDGE_TRANSPORT_HTTP) {
284
+ /* Initialize WinHTTP session */
285
+ bridge->hSession = WinHttpOpen(
286
+ L"SovereignIDE/1.0",
287
+ WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
288
+ WINHTTP_NO_PROXY_NAME,
289
+ WINHTTP_NO_PROXY_BYPASS,
290
+ 0
291
+ );
292
+
293
+ if (!bridge->hSession) {
294
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
295
+ "Failed to initialize WinHTTP");
296
+ free(bridge);
297
+ return NULL;
298
+ }
299
+
300
+ /* Set timeout */
301
+ DWORD timeout = config->request_timeout ? config->request_timeout : 30000;
302
+ WinHttpSetTimeouts(bridge->hSession, timeout, timeout, timeout, timeout);
303
+
304
+ /* Connect to host */
305
+ wchar_t host[256];
306
+ MultiByteToWideChar(CP_UTF8, 0, config->http_host, -1, host, 256);
307
+
308
+ bridge->hConnect = WinHttpConnect(
309
+ bridge->hSession,
310
+ host,
311
+ config->http_port,
312
+ 0
313
+ );
314
+
315
+ if (!bridge->hConnect) {
316
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
317
+ "Failed to connect to %s:%d", config->http_host, config->http_port);
318
+ WinHttpCloseHandle(bridge->hSession);
319
+ free(bridge);
320
+ return NULL;
321
+ }
322
+
323
+ bridge->ready = true;
324
+ }
325
+
326
+ return bridge;
327
+ }
328
+
329
+ void bridge_shutdown(Bridge* bridge) {
330
+ if (!bridge) return;
331
+
332
+ if (bridge->hConnect) {
333
+ WinHttpCloseHandle(bridge->hConnect);
334
+ }
335
+
336
+ if (bridge->hSession) {
337
+ WinHttpCloseHandle(bridge->hSession);
338
+ }
339
+
340
+ free(bridge);
341
+ }
342
+
343
+ bool bridge_is_ready(Bridge* bridge) {
344
+ return bridge && bridge->ready;
345
+ }
346
+
347
+ char* bridge_agent_run(Bridge* bridge, const char* task) {
348
+ if (!bridge || !task) return NULL;
349
+
350
+ /* Validate input length */
351
+ if (!validate_input_length(bridge, task, "Task")) {
352
+ return NULL;
353
+ }
354
+
355
+ /* Escape JSON special characters */
356
+ char* escaped_task = json_escape(task);
357
+ if (!escaped_task) {
358
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
359
+ "Out of memory during JSON escaping");
360
+ return NULL;
361
+ }
362
+
363
+ /* Build request JSON */
364
+ char request_body[4096];
365
+ snprintf(request_body, sizeof(request_body),
366
+ "{\"task\":\"%s\"}", escaped_task);
367
+
368
+ free(escaped_task);
369
+
370
+ /* POST to /agent/run */
371
+ return http_post(bridge, "/agent/run", request_body);
372
+ }
373
+
374
+ char* bridge_tool_execute(Bridge* bridge, const char* tool_name, const char* args_json) {
375
+ if (!bridge || !tool_name) return NULL;
376
+
377
+ /* Build request */
378
+ char request_body[4096];
379
+ snprintf(request_body, sizeof(request_body),
380
+ "{\"tool\":\"%s\",\"args\":%s}",
381
+ tool_name, args_json ? args_json : "{}");
382
+
383
+ return http_post(bridge, "/tool/execute", request_body);
384
+ }
385
+
386
+ char* bridge_tools_list(Bridge* bridge) {
387
+ if (!bridge) return NULL;
388
+ return http_get(bridge, "/tools");
389
+ }
390
+
391
+ char* bridge_chat_message(Bridge* bridge, const char* message) {
392
+ if (!bridge || !message) return NULL;
393
+
394
+ /* Validate input length */
395
+ if (!validate_input_length(bridge, message, "Message")) {
396
+ return NULL;
397
+ }
398
+
399
+ /* Escape JSON special characters */
400
+ char* escaped_message = json_escape(message);
401
+ if (!escaped_message) {
402
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
403
+ "Out of memory during JSON escaping");
404
+ return NULL;
405
+ }
406
+
407
+ char request_body[4096];
408
+ snprintf(request_body, sizeof(request_body),
409
+ "{\"message\":\"%s\"}", escaped_message);
410
+
411
+ free(escaped_message);
412
+
413
+ return http_post(bridge, "/chat", request_body);
414
+ }
415
+
416
+ bool bridge_health_check(Bridge* bridge) {
417
+ if (!bridge) return false;
418
+
419
+ char* response = http_get(bridge, "/health");
420
+ if (!response) return false;
421
+
422
+ /* Check for "ok" in response */
423
+ bool ok = strstr(response, "ok") != NULL;
424
+ free(response);
425
+
426
+ return ok;
427
+ }
428
+
429
+ const char* bridge_get_error(Bridge* bridge) {
430
+ return bridge ? bridge->error_msg : "Bridge is NULL";
431
+ }
432
+
433
+ char* bridge_set_key(Bridge* bridge, const char* provider, const char* api_key) {
434
+ if (!bridge || !provider || !api_key) return NULL;
435
+
436
+ /* Validate input lengths */
437
+ if (!validate_input_length(bridge, provider, "Provider")) {
438
+ return NULL;
439
+ }
440
+ if (!validate_input_length(bridge, api_key, "API key")) {
441
+ return NULL;
442
+ }
443
+
444
+ /* Escape JSON special characters */
445
+ char* escaped_provider = json_escape(provider);
446
+ char* escaped_key = json_escape(api_key);
447
+
448
+ if (!escaped_provider || !escaped_key) {
449
+ free(escaped_provider);
450
+ free(escaped_key);
451
+ snprintf(bridge->error_msg, sizeof(bridge->error_msg),
452
+ "Out of memory during JSON escaping");
453
+ return NULL;
454
+ }
455
+
456
+ /* Build request JSON */
457
+ char request_body[4096];
458
+ snprintf(request_body, sizeof(request_body),
459
+ "{\"provider\":\"%s\",\"key\":\"%s\"}",
460
+ escaped_provider, escaped_key);
461
+
462
+ free(escaped_provider);
463
+ free(escaped_key);
464
+
465
+ return http_post(bridge, "/keys/set", request_body);
466
+ }
467
+
468
+ char* bridge_get_key_status(Bridge* bridge) {
469
+ if (!bridge) return NULL;
470
+ return http_get(bridge, "/keys/status");
471
+ }
472
+
473
+ char* bridge_delete_key(Bridge* bridge, const char* provider) {
474
+ if (!bridge || !provider) return NULL;
475
+
476
+ /* Build path */
477
+ char path[256];
478
+ snprintf(path, sizeof(path), "/keys/%s", provider);
479
+
480
+ /* Need to implement DELETE - for now use POST with _method */
481
+ char request_body[256];
482
+ snprintf(request_body, sizeof(request_body), "{\"_method\":\"DELETE\"}");
483
+
484
+ return http_post(bridge, path, request_body);
485
+ }
ide/native/bridge/example.c ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Bridge Usage Example
3
+ * Demonstrates how to use the C → Python bridge
4
+ */
5
+
6
+ #include "bridge.h"
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+
10
+ int main(int argc, char** argv) {
11
+ /* Configure HTTP bridge */
12
+ BridgeConfig config = {
13
+ .transport = BRIDGE_TRANSPORT_HTTP,
14
+ .http_host = "127.0.0.1",
15
+ .http_port = 19000,
16
+ .request_timeout = 30000 /* 30 seconds */
17
+ };
18
+
19
+ /* Initialize bridge */
20
+ printf("Initializing bridge...\n");
21
+ Bridge* bridge = bridge_init(&config);
22
+ if (!bridge) {
23
+ fprintf(stderr, "Failed to initialize bridge\n");
24
+ return 1;
25
+ }
26
+
27
+ /* Health check */
28
+ printf("Health check...\n");
29
+ if (bridge_health_check(bridge)) {
30
+ printf("✓ Bridge is healthy\n\n");
31
+ } else {
32
+ fprintf(stderr, "✗ Bridge health check failed: %s\n",
33
+ bridge_get_error(bridge));
34
+ bridge_shutdown(bridge);
35
+ return 1;
36
+ }
37
+
38
+ /* List tools */
39
+ printf("Listing available tools...\n");
40
+ char* tools = bridge_tools_list(bridge);
41
+ if (tools) {
42
+ printf("Tools: %s\n\n", tools);
43
+ free(tools);
44
+ }
45
+
46
+ /* Send chat message */
47
+ printf("Sending chat message...\n");
48
+ char* chat_response = bridge_chat_message(bridge, "Hello from C!");
49
+ if (chat_response) {
50
+ printf("Response: %s\n\n", chat_response);
51
+ free(chat_response);
52
+ }
53
+
54
+ /* Run agent task */
55
+ printf("Running agent task...\n");
56
+ char* agent_result = bridge_agent_run(
57
+ bridge,
58
+ "List all files in the current directory"
59
+ );
60
+
61
+ if (agent_result) {
62
+ printf("Agent result: %s\n\n", agent_result);
63
+ free(agent_result);
64
+ } else {
65
+ fprintf(stderr, "Agent failed: %s\n", bridge_get_error(bridge));
66
+ }
67
+
68
+ /* Execute tool */
69
+ printf("Executing tool...\n");
70
+ char* tool_result = bridge_tool_execute(
71
+ bridge,
72
+ "filesystem.read",
73
+ "{\"path\":\"README.md\"}"
74
+ );
75
+
76
+ if (tool_result) {
77
+ printf("Tool result: %s\n\n", tool_result);
78
+ free(tool_result);
79
+ }
80
+
81
+ /* Cleanup */
82
+ bridge_shutdown(bridge);
83
+ printf("Bridge shutdown complete\n");
84
+
85
+ return 0;
86
+ }
ide/native/bridge/test_keys.c ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * API Key Management Test
3
+ * Demonstrates setting keys via bridge
4
+ */
5
+
6
+ #include "bridge.h"
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+ #include <string.h>
10
+
11
+ void print_status(Bridge* bridge) {
12
+ printf("\n=== API Key Status ===\n");
13
+ char* status = bridge_get_key_status(bridge);
14
+ if (status) {
15
+ printf("%s\n", status);
16
+ free(status);
17
+ } else {
18
+ printf("Failed to get status\n");
19
+ }
20
+ }
21
+
22
+ int main(int argc, char** argv) {
23
+ /* Configure bridge */
24
+ BridgeConfig config = {
25
+ .transport = BRIDGE_TRANSPORT_HTTP,
26
+ .http_host = "127.0.0.1",
27
+ .http_port = 19000,
28
+ .request_timeout = 30000
29
+ };
30
+
31
+ /* Initialize */
32
+ printf("Initializing bridge...\n");
33
+ Bridge* bridge = bridge_init(&config);
34
+ if (!bridge) {
35
+ fprintf(stderr, "Failed to initialize bridge\n");
36
+ return 1;
37
+ }
38
+
39
+ /* Health check */
40
+ if (!bridge_health_check(bridge)) {
41
+ fprintf(stderr, "Bridge not healthy\n");
42
+ bridge_shutdown(bridge);
43
+ return 1;
44
+ }
45
+ printf("OK: Bridge healthy\n");
46
+
47
+ /* Get initial status */
48
+ print_status(bridge);
49
+
50
+ /* Simulate UI - set OpenRouter key */
51
+ printf("\n--- Setting OpenRouter Key ---\n");
52
+ printf("(In real UI, user would paste their key here)\n");
53
+
54
+ const char* demo_key = "sk-or-v1-demo-key-12345"; /* Demo key */
55
+ char* set_result = bridge_set_key(bridge, "openrouter", demo_key);
56
+ if (set_result) {
57
+ printf("Result: %s\n", set_result);
58
+ free(set_result);
59
+ }
60
+
61
+ /* Get updated status */
62
+ print_status(bridge);
63
+
64
+ /* Test chat with key set */
65
+ printf("\n--- Testing Chat (should use OpenRouter) ---\n");
66
+ char* chat_reply = bridge_chat_message(bridge, "Hello! Test message.");
67
+ if (chat_reply) {
68
+ printf("Reply: %s\n", chat_reply);
69
+ free(chat_reply);
70
+ } else {
71
+ printf("Chat failed (expected - demo key not real)\n");
72
+ }
73
+
74
+ /* Delete key */
75
+ printf("\n--- Deleting OpenRouter Key ---\n");
76
+ char* delete_result = bridge_delete_key(bridge, "openrouter");
77
+ if (delete_result) {
78
+ printf("Result: %s\n", delete_result);
79
+ free(delete_result);
80
+ }
81
+
82
+ /* Final status */
83
+ print_status(bridge);
84
+
85
+ /* Cleanup */
86
+ bridge_shutdown(bridge);
87
+ printf("\nDone\n");
88
+
89
+ return 0;
90
+ }
ide/native/bridge/test_tools.c ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Tool Execution Test
3
+ * Tests filesystem and code tools
4
+ */
5
+
6
+ #include "bridge.h"
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+
10
+ int main(int argc, char** argv) {
11
+ /* Configure bridge */
12
+ BridgeConfig config = {
13
+ .transport = BRIDGE_TRANSPORT_HTTP,
14
+ .http_host = "127.0.0.1",
15
+ .http_port = 19000,
16
+ .request_timeout = 30000
17
+ };
18
+
19
+ /* Initialize */
20
+ printf("Initializing bridge...\n");
21
+ Bridge* bridge = bridge_init(&config);
22
+ if (!bridge) {
23
+ fprintf(stderr, "Failed to initialize bridge\n");
24
+ return 1;
25
+ }
26
+
27
+ /* Health check */
28
+ if (!bridge_health_check(bridge)) {
29
+ fprintf(stderr, "Bridge not healthy\n");
30
+ bridge_shutdown(bridge);
31
+ return 1;
32
+ }
33
+ printf("OK: Bridge healthy\n\n");
34
+
35
+ /* List available tools */
36
+ printf("=== Available Tools ===\n");
37
+ char* tools = bridge_tools_list(bridge);
38
+ if (tools) {
39
+ printf("%s\n\n", tools);
40
+ free(tools);
41
+ }
42
+
43
+ /* Test filesystem.read */
44
+ printf("=== Test: filesystem.read ===\n");
45
+ char* read_result = bridge_tool_execute(
46
+ bridge,
47
+ "filesystem.read",
48
+ "{\"path\":\"README.md\"}"
49
+ );
50
+ if (read_result) {
51
+ printf("Result: %s\n\n", read_result);
52
+ free(read_result);
53
+ }
54
+
55
+ /* Test filesystem.write */
56
+ printf("=== Test: filesystem.write ===\n");
57
+ char* write_result = bridge_tool_execute(
58
+ bridge,
59
+ "filesystem.write",
60
+ "{\"path\":\"test_output.txt\",\"content\":\"Hello from C!\\nBridge is working.\"}"
61
+ );
62
+ if (write_result) {
63
+ printf("Result: %s\n\n", write_result);
64
+ free(write_result);
65
+ }
66
+
67
+ /* Test filesystem.list */
68
+ printf("=== Test: filesystem.list ===\n");
69
+ char* list_result = bridge_tool_execute(
70
+ bridge,
71
+ "filesystem.list",
72
+ "{\"path\":\".\"}"
73
+ );
74
+ if (list_result) {
75
+ printf("Result: %s\n\n", list_result);
76
+ free(list_result);
77
+ }
78
+
79
+ /* Cleanup */
80
+ bridge_shutdown(bridge);
81
+ printf("Done\n");
82
+
83
+ return 0;
84
+ }
ide/native/chat/pipe_client.c ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Named Pipe Client
3
+ * Connects to the Elixir SovereignChat process via \\.\pipe\sovereign-ide-chat.
4
+ * Framed binary protocol per contract section 8.
5
+ */
6
+
7
+ #include "pipe_client.h"
8
+ #include "protocol.h"
9
+ #include "../core/errors.h"
10
+ #include "../core/arena.h"
11
+
12
+ #include <string.h>
13
+
14
+ #define PIPE_NAME L"\\\\.\\pipe\\sovereign-ide-chat"
15
+ #define PIPE_BUFFER_SIZE (64 * 1024)
16
+ #define RECONNECT_DELAY_MS 1000
17
+ #define MAX_RECONNECT_ATTEMPTS 10
18
+
19
+ static struct {
20
+ HANDLE pipe;
21
+ bool connected;
22
+ Arena recv_arena;
23
+ uint8_t recv_buf[PIPE_BUFFER_SIZE];
24
+ } g_pipe;
25
+
26
+ SovResult pipe_client_init(void) {
27
+ g_pipe.pipe = INVALID_HANDLE_VALUE;
28
+ g_pipe.connected = false;
29
+ arena_init(&g_pipe.recv_arena, 256 * 1024);
30
+ return SOV_OK;
31
+ }
32
+
33
+ SovResult pipe_client_connect(void) {
34
+ for (int attempt = 0; attempt < MAX_RECONNECT_ATTEMPTS; attempt++) {
35
+ g_pipe.pipe = CreateFileW(
36
+ PIPE_NAME,
37
+ GENERIC_READ | GENERIC_WRITE,
38
+ 0, NULL,
39
+ OPEN_EXISTING,
40
+ FILE_FLAG_OVERLAPPED,
41
+ NULL
42
+ );
43
+
44
+ if (g_pipe.pipe != INVALID_HANDLE_VALUE) {
45
+ DWORD mode = PIPE_READMODE_MESSAGE;
46
+ SetNamedPipeHandleState(g_pipe.pipe, &mode, NULL, NULL);
47
+ g_pipe.connected = true;
48
+ return SOV_OK;
49
+ }
50
+
51
+ if (GetLastError() != ERROR_PIPE_BUSY) {
52
+ Sleep(RECONNECT_DELAY_MS);
53
+ } else {
54
+ WaitNamedPipeW(PIPE_NAME, RECONNECT_DELAY_MS);
55
+ }
56
+ }
57
+
58
+ return SOV_ERR_PIPE;
59
+ }
60
+
61
+ SovResult pipe_client_send(const uint8_t *data, size_t len) {
62
+ if (!g_pipe.connected) return SOV_ERR_PIPE;
63
+
64
+ DWORD written;
65
+ BOOL ok = WriteFile(g_pipe.pipe, data, (DWORD)len, &written, NULL);
66
+ if (!ok || written != (DWORD)len) return SOV_ERR_PIPE;
67
+
68
+ return SOV_OK;
69
+ }
70
+
71
+ SovResult pipe_client_recv(uint8_t *out, size_t max_len, size_t *out_len) {
72
+ if (!g_pipe.connected) return SOV_ERR_PIPE;
73
+
74
+ DWORD read;
75
+ BOOL ok = ReadFile(g_pipe.pipe, out, (DWORD)max_len, &read, NULL);
76
+ if (!ok) {
77
+ DWORD err = GetLastError();
78
+ if (err == ERROR_MORE_DATA) {
79
+ *out_len = read;
80
+ return SOV_OK;
81
+ }
82
+ g_pipe.connected = false;
83
+ return SOV_ERR_PIPE;
84
+ }
85
+
86
+ *out_len = read;
87
+ return SOV_OK;
88
+ }
89
+
90
+ bool pipe_client_is_connected(void) {
91
+ return g_pipe.connected;
92
+ }
93
+
94
+ void pipe_client_disconnect(void) {
95
+ if (g_pipe.pipe != INVALID_HANDLE_VALUE) {
96
+ CloseHandle(g_pipe.pipe);
97
+ g_pipe.pipe = INVALID_HANDLE_VALUE;
98
+ }
99
+ g_pipe.connected = false;
100
+ }
101
+
102
+ void pipe_client_shutdown(void) {
103
+ pipe_client_disconnect();
104
+ arena_destroy(&g_pipe.recv_arena);
105
+ }
ide/native/chat/pipe_client.h ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_PIPE_CLIENT_H
2
+ #define SOVEREIGN_PIPE_CLIENT_H
3
+
4
+ #include <windows.h>
5
+ #include <stdint.h>
6
+ #include <stddef.h>
7
+ #include <stdbool.h>
8
+ #include "../core/errors.h"
9
+
10
+ SovResult pipe_client_init(void);
11
+ SovResult pipe_client_connect(void);
12
+ SovResult pipe_client_send(const uint8_t *data, size_t len);
13
+ SovResult pipe_client_recv(uint8_t *out, size_t max_len, size_t *out_len);
14
+ bool pipe_client_is_connected(void);
15
+ void pipe_client_disconnect(void);
16
+ void pipe_client_shutdown(void);
17
+
18
+ #endif /* SOVEREIGN_PIPE_CLIENT_H */
ide/native/chat/protocol.c ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — IPC Protocol
3
+ * CRC32 checksum for frame validation.
4
+ */
5
+
6
+ #include "protocol.h"
7
+
8
+ uint32_t protocol_checksum(const uint8_t *data, size_t len) {
9
+ uint32_t crc = 0xFFFFFFFF;
10
+ for (size_t i = 0; i < len; i++) {
11
+ crc ^= data[i];
12
+ for (int j = 0; j < 8; j++) {
13
+ crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
14
+ }
15
+ }
16
+ return ~crc;
17
+ }
ide/native/chat/protocol.h ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_PROTOCOL_H
2
+ #define SOVEREIGN_PROTOCOL_H
3
+
4
+ #include <stdint.h>
5
+ #include <stddef.h>
6
+
7
+ #define PROTOCOL_VERSION 1
8
+ #define MAX_MESSAGE_SIZE (4 * 1024 * 1024)
9
+
10
+ typedef enum MsgType {
11
+ MSG_HANDSHAKE = 0x01,
12
+ MSG_CONVERSATION_CREATE = 0x10,
13
+ MSG_CONVERSATION_MSG = 0x11,
14
+ MSG_CONVERSATION_CANCEL = 0x12,
15
+ MSG_STREAM_STARTED = 0x20,
16
+ MSG_STREAM_DELTA = 0x21,
17
+ MSG_STREAM_COMPLETED = 0x22,
18
+ MSG_TOOL_STARTED = 0x30,
19
+ MSG_TOOL_COMPLETED = 0x31,
20
+ MSG_APPROVAL_REQUESTED = 0x40,
21
+ MSG_APPROVAL_RESOLVED = 0x41,
22
+ MSG_AGENT_STATE = 0x50,
23
+ MSG_ERROR = 0xFF,
24
+ } MsgType;
25
+
26
+ #pragma pack(push, 1)
27
+ typedef struct FrameHeader {
28
+ uint8_t protocol_version;
29
+ uint8_t message_type;
30
+ uint32_t request_id;
31
+ uint32_t workspace_id;
32
+ uint32_t conversation_id;
33
+ uint32_t payload_length;
34
+ uint32_t checksum;
35
+ } FrameHeader;
36
+ #pragma pack(pop)
37
+
38
+ #define FRAME_HEADER_SIZE sizeof(FrameHeader)
39
+
40
+ uint32_t protocol_checksum(const uint8_t *data, size_t len);
41
+
42
+ #endif /* SOVEREIGN_PROTOCOL_H */
ide/native/core/arena.c ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Arena Allocator
3
+ * Bump-pointer allocator for frame-scoped allocations.
4
+ * No individual frees. Reset the arena to reclaim all memory at once.
5
+ */
6
+
7
+ #include "arena.h"
8
+ #include <string.h>
9
+ #include <assert.h>
10
+
11
+ #ifndef ARENA_DEFAULT_CAPACITY
12
+ #define ARENA_DEFAULT_CAPACITY (1024 * 1024) /* 1 MiB */
13
+ #endif
14
+
15
+ #define ARENA_ALIGN 16
16
+
17
+ static size_t align_up(size_t n, size_t align) {
18
+ return (n + align - 1) & ~(align - 1);
19
+ }
20
+
21
+ void arena_init(Arena *a, size_t capacity) {
22
+ a->base = (uint8_t *)VirtualAlloc(NULL, capacity,
23
+ MEM_RESERVE | MEM_COMMIT,
24
+ PAGE_READWRITE);
25
+ assert(a->base != NULL);
26
+ a->capacity = capacity;
27
+ a->offset = 0;
28
+ }
29
+
30
+ void arena_init_default(Arena *a) {
31
+ arena_init(a, ARENA_DEFAULT_CAPACITY);
32
+ }
33
+
34
+ void *arena_alloc(Arena *a, size_t size) {
35
+ size_t aligned = align_up(size, ARENA_ALIGN);
36
+ assert(a->offset + aligned <= a->capacity);
37
+ void *ptr = a->base + a->offset;
38
+ a->offset += aligned;
39
+ return ptr;
40
+ }
41
+
42
+ void *arena_alloc_zero(Arena *a, size_t size) {
43
+ void *ptr = arena_alloc(a, size);
44
+ memset(ptr, 0, size);
45
+ return ptr;
46
+ }
47
+
48
+ void arena_reset(Arena *a) {
49
+ a->offset = 0;
50
+ }
51
+
52
+ void arena_destroy(Arena *a) {
53
+ if (a->base) {
54
+ VirtualFree(a->base, 0, MEM_RELEASE);
55
+ a->base = NULL;
56
+ }
57
+ a->capacity = 0;
58
+ a->offset = 0;
59
+ }
60
+
61
+ ArenaMark arena_mark(Arena *a) {
62
+ return (ArenaMark){ .offset = a->offset };
63
+ }
64
+
65
+ void arena_restore(Arena *a, ArenaMark mark) {
66
+ assert(mark.offset <= a->offset);
67
+ a->offset = mark.offset;
68
+ }
ide/native/core/arena.h ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_ARENA_H
2
+ #define SOVEREIGN_ARENA_H
3
+
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+ #include <windows.h>
7
+
8
+ typedef struct Arena {
9
+ uint8_t *base;
10
+ size_t capacity;
11
+ size_t offset;
12
+ } Arena;
13
+
14
+ typedef struct ArenaMark {
15
+ size_t offset;
16
+ } ArenaMark;
17
+
18
+ void arena_init(Arena *a, size_t capacity);
19
+ void arena_init_default(Arena *a);
20
+ void *arena_alloc(Arena *a, size_t size);
21
+ void *arena_alloc_zero(Arena *a, size_t size);
22
+ void arena_reset(Arena *a);
23
+ void arena_destroy(Arena *a);
24
+
25
+ ArenaMark arena_mark(Arena *a);
26
+ void arena_restore(Arena *a, ArenaMark mark);
27
+
28
+ #endif /* SOVEREIGN_ARENA_H */
ide/native/core/errors.h ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_ERRORS_H
2
+ #define SOVEREIGN_ERRORS_H
3
+
4
+ #include <stdio.h>
5
+ #include <stdlib.h>
6
+
7
+ typedef enum SovResult {
8
+ SOV_OK = 0,
9
+ SOV_ERR_ALLOC,
10
+ SOV_ERR_IO,
11
+ SOV_ERR_PARSE,
12
+ SOV_ERR_PROTOCOL,
13
+ SOV_ERR_PIPE,
14
+ SOV_ERR_FCL_DENIED,
15
+ SOV_ERR_FCL_INVALID,
16
+ SOV_ERR_LSP,
17
+ SOV_ERR_GIT,
18
+ SOV_ERR_BUILD,
19
+ } SovResult;
20
+
21
+ #define SOV_ASSERT(cond, msg) \
22
+ do { (void)(cond); (void)(msg); } while(0)
23
+
24
+ #define SOV_TRY(expr) \
25
+ do { SovResult _r = (expr); if (_r != SOV_OK) return _r; } while(0)
26
+
27
+ #endif /* SOVEREIGN_ERRORS_H */
ide/native/core/events.c ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Event System
3
+ * Lock-free SPSC ring buffer for inter-module communication.
4
+ * C native modules post events; consumers drain per frame.
5
+ */
6
+
7
+ #include "events.h"
8
+ #include <string.h>
9
+ #include <assert.h>
10
+
11
+ #define EVENT_QUEUE_CAPACITY 4096
12
+
13
+ static struct {
14
+ Event ring[EVENT_QUEUE_CAPACITY];
15
+ volatile LONG write_pos;
16
+ volatile LONG read_pos;
17
+ } g_events;
18
+
19
+ void events_init(void) {
20
+ memset(&g_events, 0, sizeof(g_events));
21
+ }
22
+
23
+ bool event_post(Event ev) {
24
+ LONG w = g_events.write_pos;
25
+ LONG next = (w + 1) % EVENT_QUEUE_CAPACITY;
26
+ if (next == g_events.read_pos) {
27
+ return false; /* queue full */
28
+ }
29
+ g_events.ring[w] = ev;
30
+ MemoryBarrier();
31
+ InterlockedExchange(&g_events.write_pos, next);
32
+ return true;
33
+ }
34
+
35
+ bool event_poll(Event *out) {
36
+ LONG r = g_events.read_pos;
37
+ if (r == g_events.write_pos) {
38
+ return false; /* empty */
39
+ }
40
+ *out = g_events.ring[r];
41
+ MemoryBarrier();
42
+ InterlockedExchange(&g_events.read_pos, (r + 1) % EVENT_QUEUE_CAPACITY);
43
+ return true;
44
+ }
45
+
46
+ void events_drain(EventHandler handler, void *ctx) {
47
+ Event ev;
48
+ while (event_poll(&ev)) {
49
+ handler(&ev, ctx);
50
+ }
51
+ }
ide/native/core/events.h ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_EVENTS_H
2
+ #define SOVEREIGN_EVENTS_H
3
+
4
+ #include <stdbool.h>
5
+ #include <stdint.h>
6
+ #include <windows.h>
7
+
8
+ typedef enum EventKind {
9
+ EVENT_NONE = 0,
10
+ EVENT_KEY_DOWN,
11
+ EVENT_KEY_UP,
12
+ EVENT_CHAR_INPUT,
13
+ EVENT_MOUSE_MOVE,
14
+ EVENT_MOUSE_DOWN,
15
+ EVENT_MOUSE_UP,
16
+ EVENT_MOUSE_WHEEL,
17
+ EVENT_RESIZE,
18
+ EVENT_FOCUS_GAINED,
19
+ EVENT_FOCUS_LOST,
20
+ EVENT_FILE_CHANGED,
21
+ EVENT_CHAT_MESSAGE,
22
+ EVENT_TOOL_RESULT,
23
+ EVENT_APPROVAL_REQUEST,
24
+ EVENT_BUILD_OUTPUT,
25
+ EVENT_DIAGNOSTIC,
26
+ EVENT_QUIT,
27
+ } EventKind;
28
+
29
+ typedef struct Event {
30
+ EventKind kind;
31
+ uint64_t timestamp;
32
+ union {
33
+ struct { uint32_t vk; uint32_t scancode; uint32_t mods; } key;
34
+ struct { uint32_t codepoint; } ch;
35
+ struct { int32_t x; int32_t y; uint32_t buttons; } mouse;
36
+ struct { int32_t delta; } wheel;
37
+ struct { uint32_t width; uint32_t height; } resize;
38
+ struct { uint64_t id; } message;
39
+ };
40
+ } Event;
41
+
42
+ typedef void (*EventHandler)(const Event *ev, void *ctx);
43
+
44
+ void events_init(void);
45
+ bool event_post(Event ev);
46
+ bool event_poll(Event *out);
47
+ void events_drain(EventHandler handler, void *ctx);
48
+
49
+ #endif /* SOVEREIGN_EVENTS_H */
ide/native/core/strings.c ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Interned Strings
3
+ * All strings in the IDE are length-prefixed, immutable, arena-allocated.
4
+ * Interning deduplicates identical strings via FNV-1a hash table.
5
+ */
6
+
7
+ #include "strings.h"
8
+ #include "arena.h"
9
+ #include <string.h>
10
+
11
+ #define INTERN_BUCKET_COUNT 4096
12
+
13
+ static uint64_t fnv1a(const char *data, size_t len) {
14
+ uint64_t h = 0xcbf29ce484222325ULL;
15
+ for (size_t i = 0; i < len; i++) {
16
+ h ^= (uint8_t)data[i];
17
+ h *= 0x100000001b3ULL;
18
+ }
19
+ return h;
20
+ }
21
+
22
+ typedef struct InternEntry {
23
+ Str str;
24
+ struct InternEntry *next;
25
+ } InternEntry;
26
+
27
+ static struct {
28
+ Arena arena;
29
+ InternEntry *buckets[INTERN_BUCKET_COUNT];
30
+ size_t count;
31
+ } g_intern;
32
+
33
+ void strings_init(void) {
34
+ arena_init_default(&g_intern.arena);
35
+ memset(g_intern.buckets, 0, sizeof(g_intern.buckets));
36
+ g_intern.count = 0;
37
+ }
38
+
39
+ void strings_shutdown(void) {
40
+ arena_destroy(&g_intern.arena);
41
+ }
42
+
43
+ Str str_intern_len(const char *data, size_t len) {
44
+ uint64_t hash = fnv1a(data, len);
45
+ size_t bucket = hash % INTERN_BUCKET_COUNT;
46
+
47
+ for (InternEntry *e = g_intern.buckets[bucket]; e; e = e->next) {
48
+ if (e->str.len == len && memcmp(e->str.ptr, data, len) == 0) {
49
+ return e->str;
50
+ }
51
+ }
52
+
53
+ char *copy = (char *)arena_alloc(&g_intern.arena, len + 1);
54
+ memcpy(copy, data, len);
55
+ copy[len] = '\0';
56
+
57
+ InternEntry *entry = (InternEntry *)arena_alloc(&g_intern.arena, sizeof(InternEntry));
58
+ entry->str.ptr = copy;
59
+ entry->str.len = len;
60
+ entry->next = g_intern.buckets[bucket];
61
+ g_intern.buckets[bucket] = entry;
62
+ g_intern.count++;
63
+
64
+ return entry->str;
65
+ }
66
+
67
+ Str str_intern(const char *cstr) {
68
+ return str_intern_len(cstr, strlen(cstr));
69
+ }
70
+
71
+ bool str_eq(Str a, Str b) {
72
+ return a.ptr == b.ptr;
73
+ }
ide/native/core/strings.h ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_STRINGS_H
2
+ #define SOVEREIGN_STRINGS_H
3
+
4
+ #include <stddef.h>
5
+ #include <stdbool.h>
6
+
7
+ typedef struct Str {
8
+ const char *ptr;
9
+ size_t len;
10
+ } Str;
11
+
12
+ void strings_init(void);
13
+ void strings_shutdown(void);
14
+
15
+ Str str_intern(const char *cstr);
16
+ Str str_intern_len(const char *data, size_t len);
17
+ bool str_eq(Str a, Str b);
18
+
19
+ #define STR_FMT "%.*s"
20
+ #define STR_ARG(s) (int)(s).len, (s).ptr
21
+
22
+ #endif /* SOVEREIGN_STRINGS_H */
ide/native/core/threading.h ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_THREADING_H
2
+ #define SOVEREIGN_THREADING_H
3
+
4
+ #include <windows.h>
5
+ #include <stdint.h>
6
+
7
+ typedef struct SovThread {
8
+ HANDLE handle;
9
+ DWORD id;
10
+ void (*entry)(void *);
11
+ void *arg;
12
+ } SovThread;
13
+
14
+ typedef struct SovMutex {
15
+ SRWLOCK lock;
16
+ } SovMutex;
17
+
18
+ typedef struct SovCondVar {
19
+ CONDITION_VARIABLE cv;
20
+ } SovCondVar;
21
+
22
+ static inline void sov_mutex_init(SovMutex *m) { InitializeSRWLock(&m->lock); }
23
+ static inline void sov_mutex_lock(SovMutex *m) { AcquireSRWLockExclusive(&m->lock); }
24
+ static inline void sov_mutex_unlock(SovMutex *m) { ReleaseSRWLockExclusive(&m->lock); }
25
+
26
+ static inline void sov_condvar_init(SovCondVar *cv) { InitializeConditionVariable(&cv->cv); }
27
+ static inline void sov_condvar_wait(SovCondVar *cv, SovMutex *m) { SleepConditionVariableSRW(&cv->cv, &m->lock, INFINITE, 0); }
28
+ static inline void sov_condvar_signal(SovCondVar *cv) { WakeConditionVariable(&cv->cv); }
29
+ static inline void sov_condvar_broadcast(SovCondVar *cv) { WakeAllConditionVariable(&cv->cv); }
30
+
31
+ #endif /* SOVEREIGN_THREADING_H */
ide/native/editor/buffer.c ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Piece Table Buffer
3
+ * Supports insert, delete, undo/redo, line indexing.
4
+ */
5
+
6
+ #include "buffer.h"
7
+ #include "../core/arena.h"
8
+ #include <string.h>
9
+ #include <assert.h>
10
+ #include <stdlib.h>
11
+
12
+ #define ADD_BUFFER_INITIAL_CAP (256 * 1024)
13
+ #define UNDO_STACK_CAP 1024
14
+
15
+ typedef enum PieceSource { PIECE_ORIGINAL, PIECE_ADD } PieceSource;
16
+
17
+ typedef struct Piece {
18
+ PieceSource source;
19
+ size_t start;
20
+ size_t length;
21
+ struct Piece *prev;
22
+ struct Piece *next;
23
+ } Piece;
24
+
25
+ typedef enum UndoKind { UNDO_INSERT, UNDO_DELETE } UndoKind;
26
+
27
+ typedef struct UndoEntry {
28
+ UndoKind kind;
29
+ size_t offset;
30
+ size_t len;
31
+ char *deleted_text;
32
+ } UndoEntry;
33
+
34
+ struct Buffer {
35
+ Arena arena;
36
+
37
+ char *original;
38
+ size_t original_len;
39
+
40
+ char *add_buf;
41
+ size_t add_len;
42
+ size_t add_cap;
43
+
44
+ Piece sentinel;
45
+ size_t total_len;
46
+ size_t line_count;
47
+
48
+ UndoEntry undo_stack[UNDO_STACK_CAP];
49
+ int undo_top;
50
+ int redo_top;
51
+ bool dirty;
52
+ };
53
+
54
+ static const char *piece_data(const Buffer *b, const Piece *p) {
55
+ return (p->source == PIECE_ORIGINAL)
56
+ ? b->original + p->start
57
+ : b->add_buf + p->start;
58
+ }
59
+
60
+ static size_t count_newlines(const char *text, size_t len) {
61
+ size_t n = 0;
62
+ for (size_t i = 0; i < len; i++) {
63
+ if (text[i] == '\n') n++;
64
+ }
65
+ return n;
66
+ }
67
+
68
+ static void append_to_add(Buffer *b, const char *text, size_t len) {
69
+ if (b->add_len + len > b->add_cap) {
70
+ size_t new_cap = b->add_cap * 2;
71
+ while (new_cap < b->add_len + len) new_cap *= 2;
72
+ char *new_buf = (char *)arena_alloc(&b->arena, new_cap);
73
+ memcpy(new_buf, b->add_buf, b->add_len);
74
+ b->add_buf = new_buf;
75
+ b->add_cap = new_cap;
76
+ }
77
+ memcpy(b->add_buf + b->add_len, text, len);
78
+ b->add_len += len;
79
+ }
80
+
81
+ static Piece *alloc_piece(Buffer *b) {
82
+ return (Piece *)arena_alloc(&b->arena, sizeof(Piece));
83
+ }
84
+
85
+ static void find_piece_at(Buffer *b, size_t offset, Piece **out_piece, size_t *out_local) {
86
+ Piece *p = b->sentinel.next;
87
+ size_t pos = 0;
88
+ while (p != &b->sentinel && pos + p->length <= offset) {
89
+ pos += p->length;
90
+ p = p->next;
91
+ }
92
+ *out_piece = p;
93
+ *out_local = offset - pos;
94
+ }
95
+
96
+ Buffer *buffer_create(const char *text, size_t len) {
97
+ Arena a;
98
+ arena_init(&a, 8 * 1024 * 1024);
99
+
100
+ Buffer *b = (Buffer *)arena_alloc_zero(&a, sizeof(Buffer));
101
+ b->arena = a;
102
+
103
+ b->original = (char *)arena_alloc(&b->arena, len + 1);
104
+ memcpy(b->original, text, len);
105
+ b->original[len] = '\0';
106
+ b->original_len = len;
107
+
108
+ b->add_cap = ADD_BUFFER_INITIAL_CAP;
109
+ b->add_buf = (char *)arena_alloc(&b->arena, b->add_cap);
110
+ b->add_len = 0;
111
+
112
+ if (len > 0) {
113
+ Piece *first = alloc_piece(b);
114
+ first->source = PIECE_ORIGINAL;
115
+ first->start = 0;
116
+ first->length = len;
117
+ b->sentinel.next = first;
118
+ b->sentinel.prev = first;
119
+ first->prev = &b->sentinel;
120
+ first->next = &b->sentinel;
121
+ } else {
122
+ b->sentinel.next = &b->sentinel;
123
+ b->sentinel.prev = &b->sentinel;
124
+ }
125
+
126
+ b->total_len = len;
127
+ b->line_count = 1 + count_newlines(text, len);
128
+ b->undo_top = 0;
129
+ b->redo_top = 0;
130
+ b->dirty = false;
131
+
132
+ return b;
133
+ }
134
+
135
+ void buffer_destroy(Buffer *b) {
136
+ for (int i = 0; i < b->undo_top; i++) {
137
+ free(b->undo_stack[i].deleted_text);
138
+ }
139
+ Arena a = b->arena;
140
+ arena_destroy(&a);
141
+ }
142
+
143
+ size_t buffer_length(const Buffer *b) {
144
+ return b->total_len;
145
+ }
146
+
147
+ size_t buffer_line_count(const Buffer *b) {
148
+ return b->line_count;
149
+ }
150
+
151
+ bool buffer_is_dirty(const Buffer *b) {
152
+ return b->dirty;
153
+ }
154
+
155
+ void buffer_mark_clean(Buffer *b) {
156
+ b->dirty = false;
157
+ }
158
+
159
+ bool buffer_can_undo(const Buffer *b) {
160
+ return b->undo_top > 0;
161
+ }
162
+
163
+ bool buffer_can_redo(const Buffer *b) {
164
+ return b->redo_top > 0;
165
+ }
166
+
167
+ static void push_undo(Buffer *b, UndoKind kind, size_t offset, size_t len, const char *text) {
168
+ if (b->undo_top >= UNDO_STACK_CAP) {
169
+ free(b->undo_stack[0].deleted_text);
170
+ memmove(&b->undo_stack[0], &b->undo_stack[1], (UNDO_STACK_CAP - 1) * sizeof(UndoEntry));
171
+ b->undo_top--;
172
+ }
173
+ /* discard redo stack on new edit */
174
+ for (int i = b->undo_top; i < b->undo_top + b->redo_top; i++) {
175
+ if (i < UNDO_STACK_CAP) free(b->undo_stack[i].deleted_text);
176
+ }
177
+ b->redo_top = 0;
178
+
179
+ UndoEntry *e = &b->undo_stack[b->undo_top++];
180
+ e->kind = kind;
181
+ e->offset = offset;
182
+ e->len = len;
183
+ /* always copy the text — inserts need it for redo, deletes need it for undo */
184
+ if (text && len > 0) {
185
+ e->deleted_text = (char *)malloc(len);
186
+ memcpy(e->deleted_text, text, len);
187
+ } else {
188
+ e->deleted_text = NULL;
189
+ }
190
+ }
191
+
192
+ void buffer_insert(Buffer *b, size_t offset, const char *text, size_t len) {
193
+ if (len == 0) return;
194
+ if (offset > b->total_len) offset = b->total_len;
195
+
196
+ size_t add_start = b->add_len;
197
+ append_to_add(b, text, len);
198
+
199
+ Piece *p;
200
+ size_t local;
201
+ find_piece_at(b, offset, &p, &local);
202
+
203
+ Piece *new_piece = alloc_piece(b);
204
+ new_piece->source = PIECE_ADD;
205
+ new_piece->start = add_start;
206
+ new_piece->length = len;
207
+
208
+ if (p == &b->sentinel || local == 0) {
209
+ new_piece->prev = p->prev;
210
+ new_piece->next = p;
211
+ p->prev->next = new_piece;
212
+ p->prev = new_piece;
213
+ } else if (local == p->length) {
214
+ new_piece->prev = p;
215
+ new_piece->next = p->next;
216
+ p->next->prev = new_piece;
217
+ p->next = new_piece;
218
+ } else {
219
+ Piece *right = alloc_piece(b);
220
+ right->source = p->source;
221
+ right->start = p->start + local;
222
+ right->length = p->length - local;
223
+ p->length = local;
224
+
225
+ new_piece->prev = p;
226
+ new_piece->next = right;
227
+ right->prev = new_piece;
228
+ right->next = p->next;
229
+ p->next->prev = right;
230
+ p->next = new_piece;
231
+ }
232
+
233
+ b->total_len += len;
234
+ b->line_count += count_newlines(text, len);
235
+ b->dirty = true;
236
+
237
+ push_undo(b, UNDO_INSERT, offset, len, text);
238
+ }
239
+
240
+ void buffer_delete(Buffer *b, size_t offset, size_t len) {
241
+ if (len == 0 || offset >= b->total_len) return;
242
+ if (offset + len > b->total_len) len = b->total_len - offset;
243
+
244
+ /* save deleted text for undo */
245
+ char *deleted = (char *)malloc(len);
246
+ buffer_read(b, offset, deleted, len);
247
+ b->line_count -= count_newlines(deleted, len);
248
+
249
+ size_t remaining = len;
250
+ Piece *p;
251
+ size_t local;
252
+ find_piece_at(b, offset, &p, &local);
253
+
254
+ while (remaining > 0 && p != &b->sentinel) {
255
+ if (local == 0 && remaining >= p->length) {
256
+ /* remove entire piece */
257
+ Piece *next = p->next;
258
+ p->prev->next = p->next;
259
+ p->next->prev = p->prev;
260
+ remaining -= p->length;
261
+ p = next;
262
+ local = 0;
263
+ } else if (local == 0) {
264
+ /* trim from start */
265
+ p->start += remaining;
266
+ p->length -= remaining;
267
+ remaining = 0;
268
+ } else if (local + remaining >= p->length) {
269
+ /* trim from end */
270
+ size_t removed = p->length - local;
271
+ p->length = local;
272
+ remaining -= removed;
273
+ p = p->next;
274
+ local = 0;
275
+ } else {
276
+ /* split: remove middle */
277
+ Piece *right = alloc_piece(b);
278
+ right->source = p->source;
279
+ right->start = p->start + local + remaining;
280
+ right->length = p->length - local - remaining;
281
+ p->length = local;
282
+
283
+ right->next = p->next;
284
+ right->prev = p;
285
+ p->next->prev = right;
286
+ p->next = right;
287
+ remaining = 0;
288
+ }
289
+ }
290
+
291
+ b->total_len -= len;
292
+ b->dirty = true;
293
+
294
+ push_undo(b, UNDO_DELETE, offset, len, deleted);
295
+ free(deleted);
296
+ }
297
+
298
+ static void raw_insert(Buffer *b, size_t offset, const char *text, size_t len) {
299
+ size_t add_start = b->add_len;
300
+ append_to_add(b, text, len);
301
+
302
+ Piece *p;
303
+ size_t local;
304
+ find_piece_at(b, offset, &p, &local);
305
+
306
+ Piece *new_piece = alloc_piece(b);
307
+ new_piece->source = PIECE_ADD;
308
+ new_piece->start = add_start;
309
+ new_piece->length = len;
310
+
311
+ if (p == &b->sentinel || local == 0) {
312
+ new_piece->prev = p->prev;
313
+ new_piece->next = p;
314
+ p->prev->next = new_piece;
315
+ p->prev = new_piece;
316
+ } else if (local == p->length) {
317
+ new_piece->prev = p;
318
+ new_piece->next = p->next;
319
+ p->next->prev = new_piece;
320
+ p->next = new_piece;
321
+ } else {
322
+ Piece *right = alloc_piece(b);
323
+ right->source = p->source;
324
+ right->start = p->start + local;
325
+ right->length = p->length - local;
326
+ p->length = local;
327
+ new_piece->prev = p;
328
+ new_piece->next = right;
329
+ right->prev = new_piece;
330
+ right->next = p->next;
331
+ p->next->prev = right;
332
+ p->next = new_piece;
333
+ }
334
+
335
+ b->total_len += len;
336
+ b->line_count += count_newlines(text, len);
337
+ }
338
+
339
+ static void raw_delete(Buffer *b, size_t offset, size_t len) {
340
+ if (len == 0) return;
341
+ char *tmp = (char *)malloc(len);
342
+ buffer_read(b, offset, tmp, len);
343
+ b->line_count -= count_newlines(tmp, len);
344
+ free(tmp);
345
+
346
+ size_t remaining = len;
347
+ Piece *p;
348
+ size_t local;
349
+ find_piece_at(b, offset, &p, &local);
350
+
351
+ while (remaining > 0 && p != &b->sentinel) {
352
+ if (local == 0 && remaining >= p->length) {
353
+ Piece *next = p->next;
354
+ p->prev->next = p->next;
355
+ p->next->prev = p->prev;
356
+ remaining -= p->length;
357
+ p = next;
358
+ local = 0;
359
+ } else if (local == 0) {
360
+ p->start += remaining;
361
+ p->length -= remaining;
362
+ remaining = 0;
363
+ } else if (local + remaining >= p->length) {
364
+ size_t removed = p->length - local;
365
+ p->length = local;
366
+ remaining -= removed;
367
+ p = p->next;
368
+ local = 0;
369
+ } else {
370
+ Piece *right = alloc_piece(b);
371
+ right->source = p->source;
372
+ right->start = p->start + local + remaining;
373
+ right->length = p->length - local - remaining;
374
+ p->length = local;
375
+ right->next = p->next;
376
+ right->prev = p;
377
+ p->next->prev = right;
378
+ p->next = right;
379
+ remaining = 0;
380
+ }
381
+ }
382
+ b->total_len -= len;
383
+ }
384
+
385
+ void buffer_undo(Buffer *b) {
386
+ if (b->undo_top == 0) return;
387
+ b->undo_top--;
388
+ UndoEntry *e = &b->undo_stack[b->undo_top];
389
+ b->redo_top++;
390
+
391
+ if (e->kind == UNDO_INSERT) {
392
+ raw_delete(b, e->offset, e->len);
393
+ } else {
394
+ raw_insert(b, e->offset, e->deleted_text, e->len);
395
+ }
396
+ b->dirty = true;
397
+ }
398
+
399
+ void buffer_redo(Buffer *b) {
400
+ if (b->redo_top == 0) return;
401
+ UndoEntry *e = &b->undo_stack[b->undo_top];
402
+ b->undo_top++;
403
+ b->redo_top--;
404
+
405
+ if (e->kind == UNDO_INSERT) {
406
+ raw_insert(b, e->offset, e->deleted_text, e->len);
407
+ } else {
408
+ raw_delete(b, e->offset, e->len);
409
+ }
410
+ b->dirty = true;
411
+ }
412
+
413
+ size_t buffer_read(const Buffer *b, size_t offset, char *out, size_t max_len) {
414
+ size_t copied = 0;
415
+ size_t pos = 0;
416
+ const Piece *p = b->sentinel.next;
417
+
418
+ while (p != &b->sentinel && pos + p->length <= offset) {
419
+ pos += p->length;
420
+ p = p->next;
421
+ }
422
+
423
+ size_t skip = offset - pos;
424
+ while (p != &b->sentinel && copied < max_len) {
425
+ const char *data = piece_data(b, p) + skip;
426
+ size_t avail = p->length - skip;
427
+ size_t to_copy = (avail < max_len - copied) ? avail : (max_len - copied);
428
+ memcpy(out + copied, data, to_copy);
429
+ copied += to_copy;
430
+ skip = 0;
431
+ p = p->next;
432
+ }
433
+
434
+ return copied;
435
+ }
436
+
437
+ size_t buffer_line_start(const Buffer *b, size_t line) {
438
+ if (line == 0) return 0;
439
+ size_t current_line = 0;
440
+ size_t pos = 0;
441
+ const Piece *p = b->sentinel.next;
442
+
443
+ while (p != &b->sentinel) {
444
+ const char *data = piece_data(b, p);
445
+ for (size_t i = 0; i < p->length; i++) {
446
+ if (data[i] == '\n') {
447
+ current_line++;
448
+ if (current_line == line) return pos + i + 1;
449
+ }
450
+ }
451
+ pos += p->length;
452
+ p = p->next;
453
+ }
454
+ return b->total_len;
455
+ }
456
+
457
+ size_t buffer_line_length(const Buffer *b, size_t line) {
458
+ size_t start = buffer_line_start(b, line);
459
+ if (start >= b->total_len) return 0;
460
+
461
+ size_t pos = start;
462
+ const Piece *p = b->sentinel.next;
463
+ size_t piece_pos = 0;
464
+
465
+ while (p != &b->sentinel && piece_pos + p->length <= start) {
466
+ piece_pos += p->length;
467
+ p = p->next;
468
+ }
469
+
470
+ size_t skip = start - piece_pos;
471
+ while (p != &b->sentinel) {
472
+ const char *data = piece_data(b, p) + skip;
473
+ size_t avail = p->length - skip;
474
+ for (size_t i = 0; i < avail; i++) {
475
+ if (data[i] == '\n') return pos - start;
476
+ pos++;
477
+ }
478
+ skip = 0;
479
+ p = p->next;
480
+ }
481
+ return pos - start;
482
+ }
ide/native/editor/buffer.h ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_BUFFER_H
2
+ #define SOVEREIGN_BUFFER_H
3
+
4
+ #include <stddef.h>
5
+ #include <stdbool.h>
6
+
7
+ typedef struct Buffer Buffer;
8
+
9
+ Buffer *buffer_create(const char *text, size_t len);
10
+ void buffer_destroy(Buffer *b);
11
+ size_t buffer_length(const Buffer *b);
12
+ size_t buffer_line_count(const Buffer *b);
13
+ void buffer_insert(Buffer *b, size_t offset, const char *text, size_t len);
14
+ void buffer_delete(Buffer *b, size_t offset, size_t len);
15
+ size_t buffer_read(const Buffer *b, size_t offset, char *out, size_t max_len);
16
+
17
+ void buffer_undo(Buffer *b);
18
+ void buffer_redo(Buffer *b);
19
+ bool buffer_can_undo(const Buffer *b);
20
+ bool buffer_can_redo(const Buffer *b);
21
+ bool buffer_is_dirty(const Buffer *b);
22
+ void buffer_mark_clean(Buffer *b);
23
+
24
+ size_t buffer_line_start(const Buffer *b, size_t line);
25
+ size_t buffer_line_length(const Buffer *b, size_t line);
26
+
27
+ #endif /* SOVEREIGN_BUFFER_H */
ide/native/editor/document.c ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Document
3
+ * File open/save with UTF-8 + CRLF normalization.
4
+ */
5
+
6
+ #include "document.h"
7
+ #include <windows.h>
8
+ #include <stdlib.h>
9
+ #include <string.h>
10
+
11
+ void document_new(Document *doc) {
12
+ doc->buffer = buffer_create("", 0);
13
+ doc->path[0] = L'\0';
14
+ doc->has_path = false;
15
+ doc->line_ending = LE_LF;
16
+ }
17
+
18
+ static LineEnding detect_line_ending(const char *text, size_t len) {
19
+ for (size_t i = 0; i + 1 < len; i++) {
20
+ if (text[i] == '\r' && text[i+1] == '\n') return LE_CRLF;
21
+ }
22
+ for (size_t i = 0; i < len; i++) {
23
+ if (text[i] == '\r') return LE_CR;
24
+ }
25
+ return LE_LF;
26
+ }
27
+
28
+ /* Normalize CRLF/CR to LF for internal storage */
29
+ static char *normalize_to_lf(const char *src, size_t src_len, size_t *out_len) {
30
+ char *dst = (char *)malloc(src_len + 1);
31
+ size_t j = 0;
32
+ for (size_t i = 0; i < src_len; i++) {
33
+ if (src[i] == '\r') {
34
+ dst[j++] = '\n';
35
+ if (i + 1 < src_len && src[i+1] == '\n') i++;
36
+ } else {
37
+ dst[j++] = src[i];
38
+ }
39
+ }
40
+ dst[j] = '\0';
41
+ *out_len = j;
42
+ return dst;
43
+ }
44
+
45
+ SovResult document_open(Document *doc, const wchar_t *path) {
46
+ HANDLE h = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL,
47
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
48
+ if (h == INVALID_HANDLE_VALUE) return SOV_ERR_IO;
49
+
50
+ LARGE_INTEGER file_size;
51
+ GetFileSizeEx(h, &file_size);
52
+ if (file_size.QuadPart > 256 * 1024 * 1024) {
53
+ CloseHandle(h);
54
+ return SOV_ERR_IO;
55
+ }
56
+
57
+ size_t sz = (size_t)file_size.QuadPart;
58
+ char *raw = (char *)malloc(sz + 1);
59
+ DWORD nread = 0;
60
+ ReadFile(h, raw, (DWORD)sz, &nread, NULL);
61
+ CloseHandle(h);
62
+ raw[nread] = '\0';
63
+
64
+ /* Skip UTF-8 BOM if present */
65
+ const char *text = raw;
66
+ size_t text_len = nread;
67
+ if (text_len >= 3 &&
68
+ (unsigned char)text[0] == 0xEF &&
69
+ (unsigned char)text[1] == 0xBB &&
70
+ (unsigned char)text[2] == 0xBF) {
71
+ text += 3;
72
+ text_len -= 3;
73
+ }
74
+
75
+ doc->line_ending = detect_line_ending(text, text_len);
76
+
77
+ size_t norm_len;
78
+ char *norm = normalize_to_lf(text, text_len, &norm_len);
79
+ free(raw);
80
+
81
+ doc->buffer = buffer_create(norm, norm_len);
82
+ free(norm);
83
+
84
+ wcscpy_s(doc->path, MAX_PATH, path);
85
+ doc->has_path = true;
86
+ return SOV_OK;
87
+ }
88
+
89
+ SovResult document_save(Document *doc) {
90
+ if (!doc->has_path) return SOV_ERR_IO;
91
+ SovResult r = document_save_as(doc, doc->path);
92
+ return r;
93
+ }
94
+
95
+ SovResult document_save_as(Document *doc, const wchar_t *path) {
96
+ size_t len = buffer_length(doc->buffer);
97
+ char *content = (char *)malloc(len + 1);
98
+ buffer_read(doc->buffer, 0, content, len);
99
+ content[len] = '\0';
100
+
101
+ /* Re-apply original line endings */
102
+ char *output = content;
103
+ size_t out_len = len;
104
+ char *crlf_buf = NULL;
105
+
106
+ if (doc->line_ending == LE_CRLF) {
107
+ /* count newlines to allocate */
108
+ size_t nl = 0;
109
+ for (size_t i = 0; i < len; i++) if (content[i] == '\n') nl++;
110
+ crlf_buf = (char *)malloc(len + nl + 1);
111
+ size_t j = 0;
112
+ for (size_t i = 0; i < len; i++) {
113
+ if (content[i] == '\n') crlf_buf[j++] = '\r';
114
+ crlf_buf[j++] = content[i];
115
+ }
116
+ crlf_buf[j] = '\0';
117
+ output = crlf_buf;
118
+ out_len = j;
119
+ }
120
+
121
+ HANDLE h = CreateFileW(path, GENERIC_WRITE, 0, NULL,
122
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
123
+ if (h == INVALID_HANDLE_VALUE) {
124
+ free(content);
125
+ free(crlf_buf);
126
+ return SOV_ERR_IO;
127
+ }
128
+
129
+ DWORD written;
130
+ WriteFile(h, output, (DWORD)out_len, &written, NULL);
131
+ CloseHandle(h);
132
+
133
+ free(content);
134
+ free(crlf_buf);
135
+
136
+ wcscpy_s(doc->path, MAX_PATH, path);
137
+ doc->has_path = true;
138
+ buffer_mark_clean(doc->buffer);
139
+ return SOV_OK;
140
+ }
141
+
142
+ bool document_is_dirty(const Document *doc) {
143
+ return buffer_is_dirty(doc->buffer);
144
+ }
145
+
146
+ void document_destroy(Document *doc) {
147
+ if (doc->buffer) {
148
+ buffer_destroy(doc->buffer);
149
+ doc->buffer = NULL;
150
+ }
151
+ }
ide/native/editor/document.h ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_DOCUMENT_H
2
+ #define SOVEREIGN_DOCUMENT_H
3
+
4
+ #include <windows.h>
5
+ #include "buffer.h"
6
+ #include "../core/errors.h"
7
+ #include <stdbool.h>
8
+ #include <stddef.h>
9
+
10
+ typedef enum LineEnding { LE_LF, LE_CRLF, LE_CR } LineEnding;
11
+
12
+ typedef struct Document {
13
+ Buffer *buffer;
14
+ wchar_t path[MAX_PATH];
15
+ LineEnding line_ending;
16
+ bool has_path;
17
+ } Document;
18
+
19
+ SovResult document_open(Document *doc, const wchar_t *path);
20
+ SovResult document_save(Document *doc);
21
+ SovResult document_save_as(Document *doc, const wchar_t *path);
22
+ void document_new(Document *doc);
23
+ void document_destroy(Document *doc);
24
+ bool document_is_dirty(const Document *doc);
25
+
26
+ #endif
ide/native/editor/editor_view.cpp ADDED
@@ -0,0 +1,613 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Editor View
3
+ * DirectWrite text rendering, visible caret, selection, keyboard navigation.
4
+ * C++ for COM vtable calls; all public functions are extern "C".
5
+ */
6
+
7
+ #include <windows.h>
8
+ #include <d2d1.h>
9
+ #include <dwrite.h>
10
+ #include <stdint.h>
11
+ #include <stdlib.h>
12
+ #include <string.h>
13
+ #include <wchar.h>
14
+
15
+ extern "C" {
16
+ #include "editor_view.h"
17
+ #include "document.h"
18
+ #include "buffer.h"
19
+ #include "../lsp/client.h"
20
+ }
21
+
22
+ #define FONT_SIZE 14.0f
23
+ #define LINE_HEIGHT 20.0f
24
+ #define GUTTER_WIDTH 48.0f
25
+ #define PADDING_LEFT 4.0f
26
+ #define CARET_WIDTH 2.0f
27
+ #define SCROLL_LINES 3
28
+
29
+ /* Colors (dark theme) */
30
+ static const D2D1_COLOR_F C_BG = { 0.122f, 0.122f, 0.141f, 1.0f };
31
+ static const D2D1_COLOR_F C_GUTTER = { 0.098f, 0.098f, 0.118f, 1.0f };
32
+ static const D2D1_COLOR_F C_GUTTER_FG = { 0.420f, 0.420f, 0.520f, 1.0f };
33
+ static const D2D1_COLOR_F C_TEXT = { 0.859f, 0.859f, 0.859f, 1.0f };
34
+ static const D2D1_COLOR_F C_CARET = { 1.000f, 1.000f, 1.000f, 1.0f };
35
+ static const D2D1_COLOR_F C_SEL = { 0.173f, 0.369f, 0.529f, 0.6f };
36
+ static const D2D1_COLOR_F C_ACTIVE_LINE= { 1.000f, 1.000f, 1.000f, 0.04f };
37
+ static const D2D1_COLOR_F C_SQUIGGLE_E = { 1.000f, 0.260f, 0.260f, 1.0f }; /* error */
38
+ static const D2D1_COLOR_F C_SQUIGGLE_W = { 1.000f, 0.780f, 0.200f, 1.0f }; /* warning */
39
+
40
+ struct EditorView {
41
+ HWND hwnd;
42
+ Document *doc;
43
+
44
+ ID2D1Factory *d2d_factory;
45
+ ID2D1HwndRenderTarget *rt;
46
+ IDWriteFactory *dw_factory;
47
+ IDWriteTextFormat *text_format;
48
+ IDWriteTextFormat *gutter_format;
49
+
50
+ ID2D1SolidColorBrush *br_text;
51
+ ID2D1SolidColorBrush *br_gutter_fg;
52
+ ID2D1SolidColorBrush *br_caret;
53
+ ID2D1SolidColorBrush *br_sel;
54
+ ID2D1SolidColorBrush *br_active_line;
55
+ ID2D1SolidColorBrush *br_gutter_bg;
56
+ ID2D1SolidColorBrush *br_squiggle_e;
57
+ ID2D1SolidColorBrush *br_squiggle_w;
58
+
59
+ uint32_t width;
60
+ uint32_t height;
61
+ float scroll_y; /* in lines */
62
+
63
+ size_t caret; /* byte offset in buffer */
64
+ size_t sel_anchor; /* selection start; equals caret = no selection */
65
+ bool has_sel;
66
+
67
+ bool caret_visible; /* blink state */
68
+ DWORD caret_tick;
69
+
70
+ /* LSP diagnostics for current file */
71
+ LspDiagList diags;
72
+ bool has_diags;
73
+ };
74
+
75
+ static HRESULT create_brushes(EditorView *ev) {
76
+ HRESULT hr;
77
+ hr = ev->rt->CreateSolidColorBrush(C_TEXT, &ev->br_text); if (FAILED(hr)) return hr;
78
+ hr = ev->rt->CreateSolidColorBrush(C_GUTTER_FG, &ev->br_gutter_fg); if (FAILED(hr)) return hr;
79
+ hr = ev->rt->CreateSolidColorBrush(C_CARET, &ev->br_caret); if (FAILED(hr)) return hr;
80
+ hr = ev->rt->CreateSolidColorBrush(C_SEL, &ev->br_sel); if (FAILED(hr)) return hr;
81
+ hr = ev->rt->CreateSolidColorBrush(C_ACTIVE_LINE, &ev->br_active_line); if (FAILED(hr)) return hr;
82
+ hr = ev->rt->CreateSolidColorBrush(C_GUTTER, &ev->br_gutter_bg); if (FAILED(hr)) return hr;
83
+ hr = ev->rt->CreateSolidColorBrush(C_SQUIGGLE_E, &ev->br_squiggle_e); if (FAILED(hr)) return hr;
84
+ hr = ev->rt->CreateSolidColorBrush(C_SQUIGGLE_W, &ev->br_squiggle_w); if (FAILED(hr)) return hr;
85
+ return S_OK;
86
+ }
87
+
88
+ static void release_brushes(EditorView *ev) {
89
+ auto safe = [](IUnknown *p) { if (p) p->Release(); };
90
+ safe(ev->br_text); ev->br_text = nullptr;
91
+ safe(ev->br_gutter_fg); ev->br_gutter_fg = nullptr;
92
+ safe(ev->br_caret); ev->br_caret = nullptr;
93
+ safe(ev->br_sel); ev->br_sel = nullptr;
94
+ safe(ev->br_active_line); ev->br_active_line = nullptr;
95
+ safe(ev->br_gutter_bg); ev->br_gutter_bg = nullptr;
96
+ safe(ev->br_squiggle_e); ev->br_squiggle_e = nullptr;
97
+ safe(ev->br_squiggle_w); ev->br_squiggle_w = nullptr;
98
+ }
99
+
100
+ extern "C" SovResult editor_view_create(EditorView **out, HWND hwnd) {
101
+ EditorView *ev = (EditorView *)calloc(1, sizeof(EditorView));
102
+ if (!ev) return SOV_ERR_ALLOC;
103
+
104
+ ev->hwnd = hwnd;
105
+ ev->caret_visible = true;
106
+ ev->caret_tick = GetTickCount();
107
+
108
+ HRESULT hr;
109
+ hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &ev->d2d_factory);
110
+ if (FAILED(hr)) { free(ev); return SOV_ERR_ALLOC; }
111
+
112
+ hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
113
+ __uuidof(IDWriteFactory),
114
+ reinterpret_cast<IUnknown **>(&ev->dw_factory));
115
+ if (FAILED(hr)) { free(ev); return SOV_ERR_ALLOC; }
116
+
117
+ hr = ev->dw_factory->CreateTextFormat(
118
+ L"Consolas", nullptr,
119
+ DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
120
+ FONT_SIZE, L"en-us", &ev->text_format);
121
+ if (FAILED(hr)) { free(ev); return SOV_ERR_ALLOC; }
122
+
123
+ hr = ev->dw_factory->CreateTextFormat(
124
+ L"Consolas", nullptr,
125
+ DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
126
+ 11.0f, L"en-us", &ev->gutter_format);
127
+ if (FAILED(hr)) { free(ev); return SOV_ERR_ALLOC; }
128
+ ev->gutter_format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING);
129
+
130
+ RECT rc;
131
+ GetClientRect(hwnd, &rc);
132
+ ev->width = (uint32_t)(rc.right - rc.left);
133
+ ev->height = (uint32_t)(rc.bottom - rc.top);
134
+
135
+ D2D1_RENDER_TARGET_PROPERTIES rtp = D2D1::RenderTargetProperties();
136
+ D2D1_HWND_RENDER_TARGET_PROPERTIES hwp = D2D1::HwndRenderTargetProperties(
137
+ hwnd, D2D1::SizeU(ev->width, ev->height));
138
+ hr = ev->d2d_factory->CreateHwndRenderTarget(rtp, hwp, &ev->rt);
139
+ if (FAILED(hr)) { free(ev); return SOV_ERR_ALLOC; }
140
+
141
+ if (FAILED(create_brushes(ev))) { free(ev); return SOV_ERR_ALLOC; }
142
+
143
+ *out = ev;
144
+ return SOV_OK;
145
+ }
146
+
147
+ extern "C" void editor_view_destroy(EditorView *ev) {
148
+ if (!ev) return;
149
+ release_brushes(ev);
150
+ if (ev->text_format) { ev->text_format->Release(); }
151
+ if (ev->gutter_format) { ev->gutter_format->Release(); }
152
+ if (ev->rt) { ev->rt->Release(); }
153
+ if (ev->d2d_factory) { ev->d2d_factory->Release(); }
154
+ if (ev->dw_factory) { ev->dw_factory->Release(); }
155
+ free(ev);
156
+ }
157
+
158
+ extern "C" void editor_view_set_document(EditorView *ev, Document *doc) {
159
+ ev->doc = doc;
160
+ ev->caret = 0;
161
+ ev->sel_anchor = 0;
162
+ ev->has_sel = false;
163
+ ev->scroll_y = 0.0f;
164
+ }
165
+
166
+ extern "C" void editor_view_set_diagnostics(EditorView *ev, const LspDiagList *list) {
167
+ if (!ev) return;
168
+ if (list) {
169
+ ev->diags = *list;
170
+ ev->has_diags = (list->count > 0);
171
+ } else {
172
+ ev->has_diags = false;
173
+ }
174
+ InvalidateRect(ev->hwnd, nullptr, FALSE);
175
+ }
176
+
177
+ extern "C" void editor_view_resize(EditorView *ev, uint32_t w, uint32_t h) {
178
+ ev->width = w;
179
+ ev->height = h;
180
+ if (ev->rt) ev->rt->Resize(D2D1::SizeU(w, h));
181
+ }
182
+
183
+ /* Convert byte offset → (line, col) */
184
+ static void offset_to_line_col(Document *doc, size_t offset, size_t *line, size_t *col) {
185
+ char tmp[4096];
186
+ size_t pos = 0;
187
+ *line = 0;
188
+ *col = 0;
189
+ size_t buf_len = buffer_length(doc->buffer);
190
+ while (pos < offset && pos < buf_len) {
191
+ size_t chunk = offset - pos;
192
+ if (chunk > sizeof(tmp)) chunk = sizeof(tmp);
193
+ size_t n = buffer_read(doc->buffer, pos, tmp, chunk);
194
+ if (n == 0) break;
195
+ for (size_t i = 0; i < n && pos < offset; i++, pos++) {
196
+ if (tmp[i] == '\n') { (*line)++; *col = 0; }
197
+ else { (*col)++; }
198
+ }
199
+ }
200
+ }
201
+
202
+ /* Render one line of text using DirectWrite */
203
+ static void draw_line(EditorView *ev, size_t line_idx, float y,
204
+ size_t caret_line, size_t sel_start, size_t sel_end, bool has_sel) {
205
+ if (!ev->doc) return;
206
+ float x0 = GUTTER_WIDTH + PADDING_LEFT;
207
+ float view_w = (float)ev->width - x0;
208
+ if (view_w <= 0) return;
209
+
210
+ /* Active line highlight */
211
+ if (line_idx == caret_line) {
212
+ D2D1_RECT_F lr = D2D1::RectF(GUTTER_WIDTH, y, (float)ev->width, y + LINE_HEIGHT);
213
+ ev->rt->FillRectangle(lr, ev->br_active_line);
214
+ }
215
+
216
+ /* Gutter line number */
217
+ wchar_t num[16];
218
+ swprintf_s(num, 16, L"%zu", line_idx + 1);
219
+ D2D1_RECT_F gr = D2D1::RectF(0, y + 2.0f, GUTTER_WIDTH - 6.0f, y + LINE_HEIGHT);
220
+ ev->rt->DrawText(num, (UINT32)wcslen(num), ev->gutter_format, gr, ev->br_gutter_fg);
221
+
222
+ /* Read line text */
223
+ size_t line_start = buffer_line_start(ev->doc->buffer, line_idx);
224
+ size_t line_len = buffer_line_length(ev->doc->buffer, line_idx);
225
+ if (line_len == 0) return;
226
+
227
+ char utf8[4096];
228
+ size_t n = buffer_read(ev->doc->buffer, line_start, utf8, line_len < 4095 ? line_len : 4095);
229
+
230
+ /* UTF-8 → UTF-16 */
231
+ wchar_t wide[4096];
232
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8, (int)n, wide, 4095);
233
+ if (wlen <= 0) return;
234
+
235
+ /* Selection highlight */
236
+ if (has_sel && sel_start < line_start + line_len && sel_end > line_start) {
237
+ size_t s = sel_start > line_start ? sel_start - line_start : 0;
238
+ size_t e = sel_end < line_start + line_len ? sel_end - line_start : line_len;
239
+ IDWriteTextLayout *layout = nullptr;
240
+ ev->dw_factory->CreateTextLayout(wide, (UINT32)wlen,
241
+ ev->text_format, view_w, LINE_HEIGHT, &layout);
242
+ if (layout) {
243
+ DWRITE_HIT_TEST_METRICS m;
244
+ float xs, dummy;
245
+ layout->HitTestTextPosition((UINT32)s, FALSE, &xs, &dummy, &m);
246
+ float xe;
247
+ layout->HitTestTextPosition((UINT32)(e < (size_t)wlen ? e : wlen), FALSE, &xe, &dummy, &m);
248
+ D2D1_RECT_F sr = D2D1::RectF(x0 + xs, y, x0 + xe, y + LINE_HEIGHT);
249
+ ev->rt->FillRectangle(sr, ev->br_sel);
250
+ layout->Release();
251
+ }
252
+ }
253
+
254
+ /* Draw text */
255
+ D2D1_RECT_F tr = D2D1::RectF(x0, y, x0 + view_w, y + LINE_HEIGHT);
256
+ ev->rt->DrawText(wide, (UINT32)wlen, ev->text_format, tr, ev->br_text);
257
+
258
+ /* LSP squiggles */
259
+ if (ev->has_diags) {
260
+ for (int di = 0; di < ev->diags.count; di++) {
261
+ const LspDiagnostic *d = &ev->diags.entries[di];
262
+ if (d->line != (uint32_t)line_idx) continue;
263
+
264
+ ID2D1SolidColorBrush *br = (d->severity == 1) ? ev->br_squiggle_e : ev->br_squiggle_w;
265
+
266
+ /* Compute x start from col via layout hit-test */
267
+ float sq_x = x0;
268
+ if (d->col > 0) {
269
+ IDWriteTextLayout *lay = nullptr;
270
+ ev->dw_factory->CreateTextLayout(wide, (UINT32)wlen,
271
+ ev->text_format, view_w, LINE_HEIGHT, &lay);
272
+ if (lay) {
273
+ DWRITE_HIT_TEST_METRICS m;
274
+ float dummy;
275
+ UINT32 col_clamped = (d->col < (UINT32)wlen) ? d->col : (UINT32)wlen;
276
+ lay->HitTestTextPosition(col_clamped, FALSE, &sq_x, &dummy, &m);
277
+ sq_x += x0;
278
+ lay->Release();
279
+ }
280
+ }
281
+
282
+ float sq_y = y + LINE_HEIGHT - 3.0f;
283
+ float sq_end = x0 + view_w;
284
+ float step = 4.0f;
285
+ bool up = true;
286
+ for (float sx = sq_x; sx < sq_end - step; sx += step, up = !up) {
287
+ float sy0 = up ? sq_y : sq_y + 2.0f;
288
+ float sy1 = up ? sq_y + 2.0f : sq_y;
289
+ ev->rt->DrawLine(D2D1::Point2F(sx, sy0),
290
+ D2D1::Point2F(sx + step, sy1), br, 1.2f);
291
+ }
292
+ }
293
+ }
294
+ }
295
+
296
+ extern "C" void editor_view_paint(EditorView *ev, RECT client) {
297
+ if (!ev || !ev->rt) return;
298
+
299
+ /* Caret blink */
300
+ DWORD now = GetTickCount();
301
+ if (now - ev->caret_tick > 530) {
302
+ ev->caret_visible = !ev->caret_visible;
303
+ ev->caret_tick = now;
304
+ }
305
+
306
+ ev->rt->BeginDraw();
307
+ ev->rt->Clear(&C_BG);
308
+
309
+ /* Gutter background */
310
+ D2D1_RECT_F gutter_rect = D2D1::RectF(0, 0, GUTTER_WIDTH, (float)client.bottom);
311
+ ev->rt->FillRectangle(gutter_rect, ev->br_gutter_bg);
312
+
313
+ if (!ev->doc) {
314
+ ev->rt->EndDraw(nullptr, nullptr);
315
+ return;
316
+ }
317
+
318
+ size_t caret_line, caret_col;
319
+ offset_to_line_col(ev->doc, ev->caret, &caret_line, &caret_col);
320
+
321
+ size_t sel_start = ev->has_sel ? (ev->sel_anchor < ev->caret ? ev->sel_anchor : ev->caret) : 0;
322
+ size_t sel_end = ev->has_sel ? (ev->sel_anchor > ev->caret ? ev->sel_anchor : ev->caret) : 0;
323
+
324
+ size_t total_lines = buffer_line_count(ev->doc->buffer);
325
+ size_t first_line = (size_t)ev->scroll_y;
326
+ size_t visible = (size_t)((float)ev->height / LINE_HEIGHT) + 2;
327
+
328
+ for (size_t i = first_line; i < total_lines && i < first_line + visible; i++) {
329
+ float y = (float)(i - first_line) * LINE_HEIGHT;
330
+ draw_line(ev, i, y, caret_line, sel_start, sel_end, ev->has_sel);
331
+ }
332
+
333
+ /* Draw caret */
334
+ if (ev->caret_visible) {
335
+ size_t line_start = buffer_line_start(ev->doc->buffer, caret_line);
336
+ size_t char_in_line = ev->caret - line_start;
337
+
338
+ float caret_x = GUTTER_WIDTH + PADDING_LEFT;
339
+ if (char_in_line > 0) {
340
+ char utf8[1024];
341
+ size_t n = buffer_read(ev->doc->buffer, line_start, utf8, char_in_line < 1023 ? char_in_line : 1023);
342
+ wchar_t wide[1024];
343
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8, (int)n, wide, 1023);
344
+ if (wlen > 0) {
345
+ IDWriteTextLayout *layout = nullptr;
346
+ ev->dw_factory->CreateTextLayout(wide, (UINT32)wlen,
347
+ ev->text_format, 9999.0f, LINE_HEIGHT, &layout);
348
+ if (layout) {
349
+ DWRITE_TEXT_METRICS tm;
350
+ layout->GetMetrics(&tm);
351
+ caret_x += tm.width;
352
+ layout->Release();
353
+ }
354
+ }
355
+ }
356
+ float caret_y = (float)(caret_line - first_line) * LINE_HEIGHT;
357
+ D2D1_RECT_F cr = D2D1::RectF(caret_x, caret_y + 2.0f, caret_x + CARET_WIDTH, caret_y + LINE_HEIGHT - 2.0f);
358
+ ev->rt->FillRectangle(cr, ev->br_caret);
359
+ }
360
+
361
+ HRESULT hr = ev->rt->EndDraw(nullptr, nullptr);
362
+ if (hr == (HRESULT)D2DERR_RECREATE_TARGET) {
363
+ /* Device lost — recreate */
364
+ release_brushes(ev);
365
+ ev->rt->Release(); ev->rt = nullptr;
366
+ RECT rc; GetClientRect(ev->hwnd, &rc);
367
+ D2D1_RENDER_TARGET_PROPERTIES rtp = D2D1::RenderTargetProperties();
368
+ D2D1_HWND_RENDER_TARGET_PROPERTIES hwp = D2D1::HwndRenderTargetProperties(
369
+ ev->hwnd, D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top));
370
+ ev->d2d_factory->CreateHwndRenderTarget(rtp, hwp, &ev->rt);
371
+ create_brushes(ev);
372
+ }
373
+ }
374
+
375
+ /* Clamp caret into buffer */
376
+ static void clamp_caret(EditorView *ev) {
377
+ if (!ev->doc) return;
378
+ size_t len = buffer_length(ev->doc->buffer);
379
+ if (ev->caret > len) ev->caret = len;
380
+ }
381
+
382
+ /* Move caret left/right by one byte (UTF-8 aware would need more work) */
383
+ static void caret_move_left(EditorView *ev) {
384
+ if (ev->caret > 0) ev->caret--;
385
+ }
386
+
387
+ static void caret_move_right(EditorView *ev) {
388
+ if (!ev->doc) return;
389
+ if (ev->caret < buffer_length(ev->doc->buffer)) ev->caret++;
390
+ }
391
+
392
+ static void caret_move_up(EditorView *ev) {
393
+ if (!ev->doc) return;
394
+ size_t line, col;
395
+ offset_to_line_col(ev->doc, ev->caret, &line, &col);
396
+ if (line == 0) { ev->caret = 0; return; }
397
+ size_t prev_start = buffer_line_start(ev->doc->buffer, line - 1);
398
+ size_t prev_len = buffer_line_length(ev->doc->buffer, line - 1);
399
+ ev->caret = prev_start + (col < prev_len ? col : prev_len);
400
+ }
401
+
402
+ static void caret_move_down(EditorView *ev) {
403
+ if (!ev->doc) return;
404
+ size_t line, col;
405
+ offset_to_line_col(ev->doc, ev->caret, &line, &col);
406
+ size_t total = buffer_line_count(ev->doc->buffer);
407
+ if (line + 1 >= total) return;
408
+ size_t next_start = buffer_line_start(ev->doc->buffer, line + 1);
409
+ size_t next_len = buffer_line_length(ev->doc->buffer, line + 1);
410
+ ev->caret = next_start + (col < next_len ? col : next_len);
411
+ }
412
+
413
+ static void caret_home(EditorView *ev) {
414
+ if (!ev->doc) return;
415
+ size_t line, col; (void)col;
416
+ offset_to_line_col(ev->doc, ev->caret, &line, &col);
417
+ ev->caret = buffer_line_start(ev->doc->buffer, line);
418
+ }
419
+
420
+ static void caret_end(EditorView *ev) {
421
+ if (!ev->doc) return;
422
+ size_t line, col; (void)col;
423
+ offset_to_line_col(ev->doc, ev->caret, &line, &col);
424
+ ev->caret = buffer_line_start(ev->doc->buffer, line)
425
+ + buffer_line_length(ev->doc->buffer, line);
426
+ }
427
+
428
+ static void ensure_caret_visible(EditorView *ev) {
429
+ if (!ev->doc) return;
430
+ size_t line, col; (void)col;
431
+ offset_to_line_col(ev->doc, ev->caret, &line, &col);
432
+ float visible_lines = (float)ev->height / LINE_HEIGHT;
433
+ if ((float)line < ev->scroll_y) {
434
+ ev->scroll_y = (float)line;
435
+ } else if ((float)line >= ev->scroll_y + visible_lines - 1) {
436
+ ev->scroll_y = (float)line - visible_lines + 2.0f;
437
+ if (ev->scroll_y < 0) ev->scroll_y = 0;
438
+ }
439
+ }
440
+
441
+ extern "C" void editor_view_key_down(EditorView *ev, UINT vk, UINT mods) {
442
+ if (!ev->doc) return;
443
+
444
+ bool shift = (mods & 1) != 0;
445
+ bool ctrl = (mods & 2) != 0;
446
+
447
+ if (shift && !ev->has_sel) {
448
+ ev->sel_anchor = ev->caret;
449
+ ev->has_sel = true;
450
+ }
451
+
452
+ switch (vk) {
453
+ case VK_LEFT:
454
+ if (!shift && ev->has_sel) { ev->caret = ev->sel_anchor < ev->caret ? ev->sel_anchor : ev->caret; ev->has_sel = false; }
455
+ else caret_move_left(ev);
456
+ break;
457
+ case VK_RIGHT:
458
+ if (!shift && ev->has_sel) { ev->caret = ev->sel_anchor > ev->caret ? ev->sel_anchor : ev->caret; ev->has_sel = false; }
459
+ else caret_move_right(ev);
460
+ break;
461
+ case VK_UP: caret_move_up(ev); break;
462
+ case VK_DOWN: caret_move_down(ev); break;
463
+ case VK_HOME: caret_home(ev); break;
464
+ case VK_END: caret_end(ev); break;
465
+ case VK_PRIOR: /* Page Up */
466
+ for (int i = 0; i < 20; i++) caret_move_up(ev);
467
+ break;
468
+ case VK_NEXT: /* Page Down */
469
+ for (int i = 0; i < 20; i++) caret_move_down(ev);
470
+ break;
471
+ case VK_DELETE: {
472
+ if (ev->has_sel) {
473
+ size_t s = ev->sel_anchor < ev->caret ? ev->sel_anchor : ev->caret;
474
+ size_t e = ev->sel_anchor > ev->caret ? ev->sel_anchor : ev->caret;
475
+ buffer_delete(ev->doc->buffer, s, e - s);
476
+ ev->caret = s; ev->has_sel = false;
477
+ } else {
478
+ buffer_delete(ev->doc->buffer, ev->caret, 1);
479
+ }
480
+ break;
481
+ }
482
+ case VK_BACK: {
483
+ if (ev->has_sel) {
484
+ size_t s = ev->sel_anchor < ev->caret ? ev->sel_anchor : ev->caret;
485
+ size_t e = ev->sel_anchor > ev->caret ? ev->sel_anchor : ev->caret;
486
+ buffer_delete(ev->doc->buffer, s, e - s);
487
+ ev->caret = s; ev->has_sel = false;
488
+ } else if (ev->caret > 0) {
489
+ ev->caret--;
490
+ buffer_delete(ev->doc->buffer, ev->caret, 1);
491
+ }
492
+ break;
493
+ }
494
+ case 'Z':
495
+ if (ctrl) {
496
+ if (buffer_can_undo(ev->doc->buffer)) {
497
+ buffer_undo(ev->doc->buffer);
498
+ clamp_caret(ev);
499
+ }
500
+ }
501
+ break;
502
+ case 'Y':
503
+ if (ctrl) {
504
+ if (buffer_can_redo(ev->doc->buffer)) {
505
+ buffer_redo(ev->doc->buffer);
506
+ clamp_caret(ev);
507
+ }
508
+ }
509
+ break;
510
+ case 'A':
511
+ if (ctrl) {
512
+ ev->sel_anchor = 0;
513
+ ev->caret = buffer_length(ev->doc->buffer);
514
+ ev->has_sel = true;
515
+ }
516
+ break;
517
+ case 'S':
518
+ if (ctrl) document_save(ev->doc);
519
+ break;
520
+ }
521
+
522
+ if (!shift) ev->has_sel = false;
523
+ clamp_caret(ev);
524
+ ensure_caret_visible(ev);
525
+ ev->caret_visible = true;
526
+ ev->caret_tick = GetTickCount();
527
+
528
+ InvalidateRect(ev->hwnd, NULL, FALSE);
529
+ }
530
+
531
+ extern "C" void editor_view_char(EditorView *ev, wchar_t ch) {
532
+ if (!ev->doc) return;
533
+ if (ch < 0x20 && ch != '\t' && ch != '\n' && ch != '\r') return;
534
+ if (ch == '\r') ch = '\n';
535
+
536
+ if (ev->has_sel) {
537
+ size_t s = ev->sel_anchor < ev->caret ? ev->sel_anchor : ev->caret;
538
+ size_t e = ev->sel_anchor > ev->caret ? ev->sel_anchor : ev->caret;
539
+ buffer_delete(ev->doc->buffer, s, e - s);
540
+ ev->caret = s; ev->has_sel = false;
541
+ }
542
+
543
+ /* UTF-16 → UTF-8 */
544
+ char utf8[4];
545
+ int utf8_len = WideCharToMultiByte(CP_UTF8, 0, &ch, 1, utf8, 4, NULL, NULL);
546
+ if (utf8_len > 0) {
547
+ buffer_insert(ev->doc->buffer, ev->caret, utf8, (size_t)utf8_len);
548
+ ev->caret += (size_t)utf8_len;
549
+ }
550
+
551
+ ensure_caret_visible(ev);
552
+ ev->caret_visible = true;
553
+ ev->caret_tick = GetTickCount();
554
+ InvalidateRect(ev->hwnd, NULL, FALSE);
555
+ }
556
+
557
+ /* Hit test: pixel (x, y) → buffer offset */
558
+ static size_t hit_test(EditorView *ev, int px, int py) {
559
+ if (!ev->doc) return 0;
560
+
561
+ size_t line = (size_t)(ev->scroll_y + (float)py / LINE_HEIGHT);
562
+ size_t total = buffer_line_count(ev->doc->buffer);
563
+ if (line >= total) line = total - 1;
564
+
565
+ size_t line_start = buffer_line_start(ev->doc->buffer, line);
566
+ size_t line_len = buffer_line_length(ev->doc->buffer, line);
567
+ if (line_len == 0) return line_start;
568
+
569
+ float text_x = (float)px - (GUTTER_WIDTH + PADDING_LEFT);
570
+ if (text_x <= 0) return line_start;
571
+
572
+ char utf8[4096];
573
+ size_t n = buffer_read(ev->doc->buffer, line_start, utf8, line_len < 4095 ? line_len : 4095);
574
+ wchar_t wide[4096];
575
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8, (int)n, wide, 4095);
576
+ if (wlen <= 0) return line_start;
577
+
578
+ IDWriteTextLayout *layout = nullptr;
579
+ ev->dw_factory->CreateTextLayout(wide, (UINT32)wlen,
580
+ ev->text_format, 9999.0f, LINE_HEIGHT, &layout);
581
+ if (!layout) return line_start;
582
+
583
+ BOOL trailing, inside;
584
+ DWRITE_HIT_TEST_METRICS m;
585
+ layout->HitTestPoint(text_x, LINE_HEIGHT / 2.0f, &trailing, &inside, &m);
586
+ layout->Release();
587
+
588
+ size_t char_pos = m.textPosition + (trailing ? 1 : 0);
589
+ return line_start + (char_pos < line_len ? char_pos : line_len);
590
+ }
591
+
592
+ extern "C" void editor_view_mouse_down(EditorView *ev, int x, int y) {
593
+ ev->caret = hit_test(ev, x, y);
594
+ ev->sel_anchor = ev->caret;
595
+ ev->has_sel = false;
596
+ ev->caret_visible = true;
597
+ ev->caret_tick = GetTickCount();
598
+ InvalidateRect(ev->hwnd, NULL, FALSE);
599
+ }
600
+
601
+ extern "C" void editor_view_mouse_move(EditorView *ev, int x, int y, bool btn) {
602
+ if (!btn) return;
603
+ size_t new_pos = hit_test(ev, x, y);
604
+ if (new_pos != ev->caret) {
605
+ ev->caret = new_pos;
606
+ ev->has_sel = (ev->caret != ev->sel_anchor);
607
+ InvalidateRect(ev->hwnd, NULL, FALSE);
608
+ }
609
+ }
610
+
611
+ extern "C" size_t editor_view_caret_offset(const EditorView *ev) {
612
+ return ev->caret;
613
+ }
ide/native/editor/editor_view.h ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_EDITOR_VIEW_H
2
+ #define SOVEREIGN_EDITOR_VIEW_H
3
+
4
+ #include "document.h"
5
+ #include "../core/errors.h"
6
+ #include "../lsp/client.h"
7
+ #include <windows.h>
8
+ #include <stdbool.h>
9
+ #include <stdint.h>
10
+
11
+ typedef struct EditorView EditorView;
12
+
13
+ SovResult editor_view_create(EditorView **out, HWND hwnd);
14
+ void editor_view_destroy(EditorView *ev);
15
+ void editor_view_set_document(EditorView *ev, Document *doc);
16
+ void editor_view_set_diagnostics(EditorView *ev, const LspDiagList *list);
17
+ void editor_view_paint(EditorView *ev, RECT client);
18
+ void editor_view_resize(EditorView *ev, uint32_t w, uint32_t h);
19
+
20
+ void editor_view_key_down(EditorView *ev, UINT vk, UINT mods);
21
+ void editor_view_char(EditorView *ev, wchar_t ch);
22
+ void editor_view_mouse_down(EditorView *ev, int x, int y);
23
+ void editor_view_mouse_move(EditorView *ev, int x, int y, bool btn);
24
+
25
+ size_t editor_view_caret_offset(const EditorView *ev);
26
+
27
+ #endif
ide/native/fcl/evaluator.c ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — FCL Evaluator
3
+ * Default-deny. Rules evaluated in order; first match wins.
4
+ */
5
+
6
+ #include "evaluator.h"
7
+ #include <string.h>
8
+
9
+ static bool path_matches(Str pattern, Str target) {
10
+ if (pattern.len == 1 && pattern.ptr[0] == '*') return true;
11
+ if (pattern.len == target.len && memcmp(pattern.ptr, target.ptr, pattern.len) == 0) return true;
12
+
13
+ if (pattern.len >= 2 && pattern.ptr[pattern.len - 1] == '*') {
14
+ /* prefix glob: "src/ STAR" matches "src/foo.c" */
15
+ size_t prefix_len = pattern.len - 1;
16
+ if (target.len >= prefix_len && memcmp(pattern.ptr, target.ptr, prefix_len) == 0) {
17
+ return true;
18
+ }
19
+ }
20
+
21
+ if (pattern.len >= 2 && pattern.ptr[0] == '*') {
22
+ /* suffix glob: "*.exe" matches "virus.exe" */
23
+ size_t suffix_len = pattern.len - 1;
24
+ const char *suffix = pattern.ptr + 1;
25
+ if (target.len >= suffix_len &&
26
+ memcmp(target.ptr + target.len - suffix_len, suffix, suffix_len) == 0) {
27
+ return true;
28
+ }
29
+ }
30
+
31
+ return false;
32
+ }
33
+
34
+ FclVerdict fcl_evaluate(const FclRuleSet *rules, const FclRequest *req) {
35
+ for (size_t i = 0; i < rules->count; i++) {
36
+ const FclRule *r = &rules->rules[i];
37
+
38
+ if (r->action != req->action) continue;
39
+ if (!path_matches(r->subject, req->target)) continue;
40
+ if (r->agent.ptr && !str_eq(r->agent, req->agent)) continue;
41
+
42
+ switch (r->kind) {
43
+ case FCL_PERMIT:
44
+ if (r->requires_approval) return FCL_AWAIT_APPROVAL;
45
+ return FCL_ALLOW;
46
+ case FCL_DENY:
47
+ return FCL_DENY_RULE;
48
+ default:
49
+ continue;
50
+ }
51
+ }
52
+
53
+ return FCL_DENY_NO_RULE; /* default deny */
54
+ }
ide/native/fcl/evaluator.h ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_FCL_EVALUATOR_H
2
+ #define SOVEREIGN_FCL_EVALUATOR_H
3
+
4
+ #include "parser.h"
5
+ #include <stdbool.h>
6
+
7
+ typedef enum FclVerdict {
8
+ FCL_ALLOW,
9
+ FCL_DENY_RULE,
10
+ FCL_DENY_NO_RULE,
11
+ FCL_AWAIT_APPROVAL,
12
+ } FclVerdict;
13
+
14
+ typedef struct FclRequest {
15
+ FclAction action;
16
+ Str target;
17
+ Str agent;
18
+ } FclRequest;
19
+
20
+ FclVerdict fcl_evaluate(const FclRuleSet *rules, const FclRequest *req);
21
+
22
+ #endif /* SOVEREIGN_FCL_EVALUATOR_H */
ide/native/fcl/parser.h ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_FCL_PARSER_H
2
+ #define SOVEREIGN_FCL_PARSER_H
3
+
4
+ #include "../core/errors.h"
5
+ #include "../core/strings.h"
6
+ #include <stdbool.h>
7
+
8
+ typedef enum FclNodeKind {
9
+ FCL_PERMIT,
10
+ FCL_DENY,
11
+ FCL_REQUIRE,
12
+ FCL_CONSTRAIN,
13
+ FCL_BOUNDARY,
14
+ } FclNodeKind;
15
+
16
+ typedef enum FclAction {
17
+ FCL_ACT_READ,
18
+ FCL_ACT_WRITE,
19
+ FCL_ACT_EXECUTE,
20
+ FCL_ACT_GIT_READ,
21
+ FCL_ACT_GIT_MUTATE,
22
+ FCL_ACT_BUILD,
23
+ FCL_ACT_DEPLOY,
24
+ FCL_ACT_MODEL,
25
+ } FclAction;
26
+
27
+ typedef struct FclRule {
28
+ FclNodeKind kind;
29
+ FclAction action;
30
+ Str subject; /* file path or tool name */
31
+ Str agent; /* which agent requested */
32
+ bool requires_approval;
33
+ } FclRule;
34
+
35
+ typedef struct FclRuleSet {
36
+ FclRule *rules;
37
+ size_t count;
38
+ size_t capacity;
39
+ } FclRuleSet;
40
+
41
+ SovResult fcl_parse(const char *source, size_t len, FclRuleSet *out);
42
+ void fcl_ruleset_free(FclRuleSet *rs);
43
+
44
+ #endif /* SOVEREIGN_FCL_PARSER_H */
ide/native/git/repository.c ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Git repository helpers
3
+ * Reads .git/HEAD and .git/index mtime for branch + dirty detection.
4
+ * No libgit2 — pure Win32 file I/O.
5
+ */
6
+
7
+ #include "repository.h"
8
+ #include <stdlib.h>
9
+ #include <string.h>
10
+ #include <stdio.h>
11
+
12
+ /* Walk up from path looking for a .git directory/file */
13
+ static bool find_git_root(const wchar_t *start, wchar_t *root_out, size_t root_cap) {
14
+ wchar_t cur[MAX_PATH];
15
+ wcscpy_s(cur, MAX_PATH, start);
16
+
17
+ for (int depth = 0; depth < 32; depth++) {
18
+ wchar_t candidate[MAX_PATH];
19
+ swprintf_s(candidate, MAX_PATH, L"%s\\.git", cur);
20
+
21
+ DWORD attr = GetFileAttributesW(candidate);
22
+ if (attr != INVALID_FILE_ATTRIBUTES) {
23
+ wcscpy_s(root_out, root_cap, cur);
24
+ return true;
25
+ }
26
+
27
+ /* Go up one level */
28
+ wchar_t *last = wcsrchr(cur, L'\\');
29
+ if (!last || last == cur) break;
30
+ *last = L'\0';
31
+ }
32
+ return false;
33
+ }
34
+
35
+ SovResult git_repo_open(GitRepo *repo, const wchar_t *path) {
36
+ memset(repo, 0, sizeof(*repo));
37
+
38
+ if (!find_git_root(path, repo->root, MAX_PATH))
39
+ return SOV_ERR_IO;
40
+
41
+ swprintf_s(repo->git_dir, MAX_PATH, L"%s\\.git", repo->root);
42
+ repo->valid = true;
43
+ return git_repo_refresh(repo);
44
+ }
45
+
46
+ SovResult git_repo_refresh(GitRepo *repo) {
47
+ if (!repo->valid) return SOV_ERR_IO;
48
+
49
+ /* Read HEAD → branch name */
50
+ wchar_t head_path[MAX_PATH];
51
+ swprintf_s(head_path, MAX_PATH, L"%s\\HEAD", repo->git_dir);
52
+
53
+ HANDLE hf = CreateFileW(head_path, GENERIC_READ, FILE_SHARE_READ,
54
+ NULL, OPEN_EXISTING, 0, NULL);
55
+ if (hf == INVALID_HANDLE_VALUE) return SOV_ERR_IO;
56
+
57
+ char buf[256] = {0};
58
+ DWORD nread = 0;
59
+ ReadFile(hf, buf, sizeof(buf) - 1, &nread, NULL);
60
+ CloseHandle(hf);
61
+
62
+ /* "ref: refs/heads/main\n" or detached SHA */
63
+ static const char prefix[] = "ref: refs/heads/";
64
+ if (strncmp(buf, prefix, sizeof(prefix) - 1) == 0) {
65
+ const char *branch = buf + sizeof(prefix) - 1;
66
+ size_t len = strcspn(branch, "\r\n");
67
+ if (len >= 128) len = 127;
68
+ MultiByteToWideChar(CP_UTF8, 0, branch, (int)len,
69
+ repo->branch, 128);
70
+ repo->branch[len] = L'\0';
71
+ repo->detached = false;
72
+ } else {
73
+ /* Detached HEAD — show short SHA */
74
+ size_t len = strcspn(buf, "\r\n");
75
+ if (len > 8) len = 8;
76
+ MultiByteToWideChar(CP_UTF8, 0, buf, (int)len,
77
+ repo->branch, 128);
78
+ repo->branch[len] = L'\0';
79
+ repo->detached = true;
80
+ }
81
+
82
+ /* Dirty detection: compare index mtime to HEAD mtime.
83
+ * If index is newer → working tree likely has staged changes.
84
+ * Good enough for status bar; not a full diff. */
85
+ wchar_t index_path[MAX_PATH];
86
+ swprintf_s(index_path, MAX_PATH, L"%s\\index", repo->git_dir);
87
+
88
+ WIN32_FILE_ATTRIBUTE_DATA fa_index = {0}, fa_head = {0};
89
+ bool has_index = GetFileAttributesExW(index_path, GetFileExInfoStandard, &fa_index);
90
+ bool has_head = GetFileAttributesExW(head_path, GetFileExInfoStandard, &fa_head);
91
+
92
+ if (has_index && has_head) {
93
+ LONG cmp = CompareFileTime(&fa_index.ftLastWriteTime,
94
+ &fa_head.ftLastWriteTime);
95
+ repo->dirty = (cmp > 0);
96
+ } else {
97
+ repo->dirty = false;
98
+ }
99
+
100
+ return SOV_OK;
101
+ }
ide/native/git/repository.h ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_GIT_REPOSITORY_H
2
+ #define SOVEREIGN_GIT_REPOSITORY_H
3
+
4
+ #include <windows.h>
5
+ #include <stdbool.h>
6
+ #include "../core/errors.h"
7
+
8
+ typedef struct GitRepo {
9
+ wchar_t root[MAX_PATH];
10
+ wchar_t git_dir[MAX_PATH];
11
+ wchar_t branch[128];
12
+ bool detached;
13
+ bool dirty;
14
+ bool valid;
15
+ } GitRepo;
16
+
17
+ SovResult git_repo_open(GitRepo *repo, const wchar_t *path);
18
+ SovResult git_repo_refresh(GitRepo *repo);
19
+
20
+ #endif
ide/native/graphics/d2d_renderer.cpp ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Direct2D Renderer (C++ for COM vtable calls)
3
+ */
4
+
5
+ #include <windows.h>
6
+ #include <d2d1_1.h>
7
+ #include <dwrite.h>
8
+ #include <stdint.h>
9
+
10
+ extern "C" {
11
+ #include "d2d_renderer.h"
12
+ }
13
+
14
+ static struct {
15
+ ID2D1Factory *factory;
16
+ ID2D1HwndRenderTarget *target;
17
+ IDWriteFactory *dwrite_factory;
18
+ bool initialized;
19
+ } g_renderer;
20
+
21
+ extern "C" SovResult renderer_init(HWND hwnd) {
22
+ HRESULT hr = D2D1CreateFactory(
23
+ D2D1_FACTORY_TYPE_SINGLE_THREADED,
24
+ &g_renderer.factory
25
+ );
26
+ if (FAILED(hr)) return SOV_ERR_ALLOC;
27
+
28
+ RECT rc;
29
+ GetClientRect(hwnd, &rc);
30
+
31
+ D2D1_SIZE_U size = D2D1::SizeU(
32
+ (UINT32)(rc.right - rc.left),
33
+ (UINT32)(rc.bottom - rc.top)
34
+ );
35
+
36
+ D2D1_RENDER_TARGET_PROPERTIES rtprops = D2D1::RenderTargetProperties();
37
+ D2D1_HWND_RENDER_TARGET_PROPERTIES hwndprops =
38
+ D2D1::HwndRenderTargetProperties(hwnd, size);
39
+
40
+ hr = g_renderer.factory->CreateHwndRenderTarget(
41
+ rtprops, hwndprops, &g_renderer.target
42
+ );
43
+ if (FAILED(hr)) return SOV_ERR_ALLOC;
44
+
45
+ hr = DWriteCreateFactory(
46
+ DWRITE_FACTORY_TYPE_SHARED,
47
+ __uuidof(IDWriteFactory),
48
+ reinterpret_cast<IUnknown **>(&g_renderer.dwrite_factory)
49
+ );
50
+ if (FAILED(hr)) return SOV_ERR_ALLOC;
51
+
52
+ g_renderer.initialized = true;
53
+ return SOV_OK;
54
+ }
55
+
56
+ extern "C" void renderer_begin_frame(void) {
57
+ if (!g_renderer.initialized) return;
58
+ g_renderer.target->BeginDraw();
59
+ D2D1_COLOR_F bg = { 0.12f, 0.12f, 0.14f, 1.0f };
60
+ g_renderer.target->Clear(&bg);
61
+ }
62
+
63
+ extern "C" void renderer_end_frame(void) {
64
+ if (!g_renderer.initialized) return;
65
+ HRESULT hr = g_renderer.target->EndDraw();
66
+ if (hr == (HRESULT)D2DERR_RECREATE_TARGET) {
67
+ g_renderer.initialized = false;
68
+ }
69
+ }
70
+
71
+ extern "C" void renderer_resize(uint32_t width, uint32_t height) {
72
+ if (!g_renderer.initialized) return;
73
+ D2D1_SIZE_U s = D2D1::SizeU(width, height);
74
+ g_renderer.target->Resize(s);
75
+ }
76
+
77
+ extern "C" void renderer_shutdown(void) {
78
+ if (g_renderer.target) { g_renderer.target->Release(); g_renderer.target = NULL; }
79
+ if (g_renderer.factory) { g_renderer.factory->Release(); g_renderer.factory = NULL; }
80
+ if (g_renderer.dwrite_factory) { g_renderer.dwrite_factory->Release(); g_renderer.dwrite_factory = NULL; }
81
+ g_renderer.initialized = false;
82
+ }
83
+
84
+ extern "C" void *renderer_get_target(void) {
85
+ return g_renderer.target;
86
+ }
87
+
88
+ extern "C" void *renderer_get_dwrite(void) {
89
+ return g_renderer.dwrite_factory;
90
+ }
ide/native/graphics/d2d_renderer.h ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_D2D_RENDERER_H
2
+ #define SOVEREIGN_D2D_RENDERER_H
3
+
4
+ #include <windows.h>
5
+ #include <stdint.h>
6
+ #include "../core/errors.h"
7
+
8
+ SovResult renderer_init(HWND hwnd);
9
+ void renderer_begin_frame(void);
10
+ void renderer_end_frame(void);
11
+ void renderer_resize(uint32_t width, uint32_t height);
12
+ void renderer_shutdown(void);
13
+
14
+ void *renderer_get_target(void);
15
+ void *renderer_get_dwrite(void);
16
+
17
+ #endif /* SOVEREIGN_D2D_RENDERER_H */
ide/native/lsp/client.c ADDED
@@ -0,0 +1,422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — LSP Client
3
+ * JSON-RPC 2.0 over stdio pipes to clangd (or any LSP server).
4
+ * Handles: initialize, initialized, textDocument/didOpen, didChange,
5
+ * shutdown, and inbound publishDiagnostics notifications.
6
+ *
7
+ * JSON is hand-built (no external lib). Parsing is minimal: we walk
8
+ * the response bytes looking for the fields we need.
9
+ */
10
+
11
+ #include "client.h"
12
+ #include <stdlib.h>
13
+ #include <string.h>
14
+ #include <stdio.h>
15
+
16
+ /* ── JSON helpers ────────────────────────────────────────────── */
17
+
18
+ /* Escape a UTF-8 string into a JSON string (caller provides buf). */
19
+ static int json_escape(const char *src, char *dst, int cap) {
20
+ int n = 0;
21
+ for (; *src && n < cap - 2; src++) {
22
+ unsigned char c = (unsigned char)*src;
23
+ if (c == '"') { dst[n++]='\\'; dst[n++]='"'; }
24
+ else if (c == '\\') { dst[n++]='\\'; dst[n++]='\\'; }
25
+ else if (c == '\n') { dst[n++]='\\'; dst[n++]='n'; }
26
+ else if (c == '\r') { dst[n++]='\\'; dst[n++]='r'; }
27
+ else if (c == '\t') { dst[n++]='\\'; dst[n++]='t'; }
28
+ else { dst[n++]=(char)c; }
29
+ }
30
+ dst[n] = '\0';
31
+ return n;
32
+ }
33
+
34
+ /* Wide path → file:/// URI in UTF-8 */
35
+ static void path_to_uri(const wchar_t *path, char *uri, int cap) {
36
+ char narrow[MAX_PATH * 2];
37
+ WideCharToMultiByte(CP_UTF8, 0, path, -1, narrow, sizeof(narrow), NULL, NULL);
38
+ /* Replace backslashes */
39
+ for (char *p = narrow; *p; p++) if (*p == '\\') *p = '/';
40
+ snprintf(uri, (size_t)cap, "file:///%s", narrow);
41
+ }
42
+
43
+ /* ── Minimal JSON parser helpers ─────────────────────────────── */
44
+
45
+ static const char *json_find_key(const char *json, const char *key) {
46
+ char needle[128];
47
+ snprintf(needle, sizeof(needle), "\"%s\"", key);
48
+ return strstr(json, needle);
49
+ }
50
+
51
+ static int json_int_after_key(const char *json, const char *key, int def) {
52
+ const char *p = json_find_key(json, key);
53
+ if (!p) return def;
54
+ p += strlen(key) + 2;
55
+ while (*p == ' ' || *p == ':' || *p == '"') p++;
56
+ if (*p == '\0') return def;
57
+ return atoi(p);
58
+ }
59
+
60
+ static bool json_str_after_key(const char *json, const char *key,
61
+ char *out, int cap) {
62
+ const char *p = json_find_key(json, key);
63
+ if (!p) return false;
64
+ p += strlen(key) + 2;
65
+ while (*p == ' ' || *p == ':') p++;
66
+ if (*p != '"') return false;
67
+ p++;
68
+ int n = 0;
69
+ while (*p && *p != '"' && n < cap - 1) {
70
+ if (*p == '\\' && *(p+1)) { p++; }
71
+ out[n++] = *p++;
72
+ }
73
+ out[n] = '\0';
74
+ return true;
75
+ }
76
+
77
+ /* ── RPC framing ─────────────────────────────────────────────── */
78
+
79
+ struct LspClient {
80
+ HANDLE pipe_stdin; /* write to child stdin */
81
+ HANDLE pipe_stdout; /* read from child stdout */
82
+ PROCESS_INFORMATION pi;
83
+ bool alive;
84
+
85
+ int next_id;
86
+ lsp_diag_cb on_diag;
87
+ void *cb_ctx;
88
+
89
+ /* Read buffer for partial Content-Length frames */
90
+ char rbuf[1 << 20]; /* 1 MB */
91
+ int rbuf_len;
92
+ };
93
+
94
+ static SovResult lsp_send(LspClient *c, const char *body, int body_len) {
95
+ char header[64];
96
+ int hlen = snprintf(header, sizeof(header),
97
+ "Content-Length: %d\r\n\r\n", body_len);
98
+ DWORD written;
99
+ if (!WriteFile(c->pipe_stdin, header, (DWORD)hlen, &written, NULL))
100
+ return SOV_ERR_IO;
101
+ if (!WriteFile(c->pipe_stdin, body, (DWORD)body_len, &written, NULL))
102
+ return SOV_ERR_IO;
103
+ return SOV_OK;
104
+ }
105
+
106
+ /* Build a JSON-RPC request and send it */
107
+ static SovResult lsp_request(LspClient *c, const char *method,
108
+ const char *params_json) {
109
+ char body[1 << 18];
110
+ int n = snprintf(body, sizeof(body),
111
+ "{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"%s\",\"params\":%s}",
112
+ c->next_id++, method, params_json ? params_json : "null");
113
+ return lsp_send(c, body, n);
114
+ }
115
+
116
+ /* Build a JSON-RPC notification (no id) and send it */
117
+ static SovResult lsp_notify(LspClient *c, const char *method,
118
+ const char *params_json) {
119
+ char body[1 << 18];
120
+ int n = snprintf(body, sizeof(body),
121
+ "{\"jsonrpc\":\"2.0\",\"method\":\"%s\",\"params\":%s}",
122
+ method, params_json ? params_json : "{}");
123
+ return lsp_send(c, body, n);
124
+ }
125
+
126
+ /* ── Diagnostics parser ──────────────────────────────────────── */
127
+
128
+ static void parse_diagnostics(LspClient *c, const char *json) {
129
+ LspDiagList list;
130
+ memset(&list, 0, sizeof(list));
131
+
132
+ /* Extract URI */
133
+ char uri[MAX_PATH * 2] = {0};
134
+ json_str_after_key(json, "uri", uri, sizeof(uri));
135
+ /* Strip file:/// prefix */
136
+ const char *path = uri;
137
+ if (strncmp(path, "file:///", 8) == 0) path += 8;
138
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, list.uri, MAX_PATH);
139
+ /* Restore backslashes on Windows */
140
+ for (wchar_t *p = list.uri; *p; p++) if (*p == L'/') *p = L'\\';
141
+
142
+ /* Walk diagnostics array */
143
+ const char *p = strstr(json, "\"diagnostics\"");
144
+ if (!p) goto done;
145
+ p = strchr(p, '[');
146
+ if (!p) goto done;
147
+ p++;
148
+
149
+ while (*p && list.count < LSP_MAX_DIAG) {
150
+ /* Find next object */
151
+ const char *obj = strchr(p, '{');
152
+ if (!obj) break;
153
+ /* Find matching } — naive single-level scan */
154
+ const char *end = obj + 1;
155
+ int depth = 1;
156
+ while (*end && depth > 0) {
157
+ if (*end == '{') depth++;
158
+ else if (*end == '}') depth--;
159
+ end++;
160
+ }
161
+ if (depth != 0) break;
162
+
163
+ /* Extract a diagnostic object [obj, end) */
164
+ char obj_buf[2048];
165
+ int obj_len = (int)(end - obj);
166
+ if (obj_len > (int)sizeof(obj_buf) - 1) obj_len = (int)sizeof(obj_buf) - 1;
167
+ memcpy(obj_buf, obj, (size_t)obj_len);
168
+ obj_buf[obj_len] = '\0';
169
+
170
+ LspDiagnostic *d = &list.entries[list.count];
171
+
172
+ /* line/character from "start" */
173
+ const char *rng = strstr(obj_buf, "\"range\"");
174
+ if (rng) {
175
+ const char *start = strstr(rng, "\"start\"");
176
+ if (start) {
177
+ d->line = (uint32_t)json_int_after_key(start, "line", 0);
178
+ d->col = (uint32_t)json_int_after_key(start, "character", 0);
179
+ }
180
+ }
181
+ d->severity = (uint32_t)json_int_after_key(obj_buf, "severity", 1);
182
+ json_str_after_key(obj_buf, "message", d->message, 512);
183
+ wcscpy_s(d->file, MAX_PATH, list.uri);
184
+
185
+ list.count++;
186
+ p = end;
187
+ }
188
+
189
+ done:
190
+ if (c->on_diag) c->on_diag(&list, c->cb_ctx);
191
+ }
192
+
193
+ /* ── Dispatch one complete JSON message ──────────────────────── */
194
+
195
+ static void dispatch_message(LspClient *c, const char *json) {
196
+ /* Only care about notifications right now */
197
+ const char *method_pos = strstr(json, "\"method\"");
198
+ if (!method_pos) return;
199
+
200
+ char method[128] = {0};
201
+ json_str_after_key(json, "method", method, sizeof(method));
202
+
203
+ if (strcmp(method, "textDocument/publishDiagnostics") == 0) {
204
+ parse_diagnostics(c, json);
205
+ }
206
+ /* Other notifications (window/logMessage, $/progress, etc.) ignored */
207
+ }
208
+
209
+ /* ── Public API ──────────────────────────────────────────────── */
210
+
211
+ SovResult lsp_client_start(LspClient **out, const wchar_t *server_cmd,
212
+ lsp_diag_cb on_diag, void *cb_ctx) {
213
+ LspClient *c = (LspClient *)calloc(1, sizeof(LspClient));
214
+ if (!c) return SOV_ERR_ALLOC;
215
+ c->on_diag = on_diag;
216
+ c->cb_ctx = cb_ctx;
217
+ c->next_id = 1;
218
+ c->alive = false;
219
+
220
+ SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE };
221
+
222
+ HANDLE child_stdin_rd, child_stdin_wr;
223
+ HANDLE child_stdout_rd, child_stdout_wr;
224
+
225
+ if (!CreatePipe(&child_stdin_rd, &child_stdin_wr, &sa, 0)) goto fail;
226
+ if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0)) {
227
+ CloseHandle(child_stdin_rd); CloseHandle(child_stdin_wr); goto fail;
228
+ }
229
+ SetHandleInformation(child_stdin_wr, HANDLE_FLAG_INHERIT, 0);
230
+ SetHandleInformation(child_stdout_rd, HANDLE_FLAG_INHERIT, 0);
231
+
232
+ c->pipe_stdin = child_stdin_wr;
233
+ c->pipe_stdout = child_stdout_rd;
234
+
235
+ STARTUPINFOW si = {0};
236
+ si.cb = sizeof(si);
237
+ si.dwFlags = STARTF_USESTDHANDLES;
238
+ si.hStdInput = child_stdin_rd;
239
+ si.hStdOutput = child_stdout_wr;
240
+ si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
241
+
242
+ wchar_t cmd[MAX_PATH + 64];
243
+ wcscpy_s(cmd, MAX_PATH + 64, server_cmd);
244
+
245
+ BOOL ok = CreateProcessW(NULL, cmd, NULL, NULL, TRUE,
246
+ CREATE_NO_WINDOW, NULL, NULL, &si, &c->pi);
247
+
248
+ CloseHandle(child_stdin_rd);
249
+ CloseHandle(child_stdout_wr);
250
+
251
+ if (!ok) goto fail;
252
+
253
+ c->alive = true;
254
+ *out = c;
255
+ return SOV_OK;
256
+
257
+ fail:
258
+ free(c);
259
+ return SOV_ERR_IO;
260
+ }
261
+
262
+ SovResult lsp_client_initialize(LspClient *c, const wchar_t *root_path) {
263
+ if (!c->alive) return SOV_ERR_IO;
264
+
265
+ char root_uri[MAX_PATH * 3];
266
+ path_to_uri(root_path, root_uri, sizeof(root_uri));
267
+
268
+ char params[MAX_PATH * 3 + 256];
269
+ snprintf(params, sizeof(params),
270
+ "{\"processId\":%lu,"
271
+ "\"rootUri\":\"%s\","
272
+ "\"capabilities\":{"
273
+ "\"textDocument\":{"
274
+ "\"publishDiagnostics\":{\"relatedInformation\":false}"
275
+ "}"
276
+ "}}",
277
+ (unsigned long)GetCurrentProcessId(), root_uri);
278
+
279
+ SovResult r = lsp_request(c, "initialize", params);
280
+ if (r != SOV_OK) return r;
281
+
282
+ /* Send initialized after a tick (clangd expects response first,
283
+ * but we're async — send immediately, clangd handles it fine) */
284
+ return lsp_notify(c, "initialized", "{}");
285
+ }
286
+
287
+ SovResult lsp_client_open(LspClient *c, const wchar_t *path,
288
+ const char *text, size_t len) {
289
+ if (!c->alive) return SOV_ERR_IO;
290
+
291
+ char uri[MAX_PATH * 3];
292
+ path_to_uri(path, uri, sizeof(uri));
293
+
294
+ /* Detect language from extension */
295
+ const char *lang = "c";
296
+ const wchar_t *ext = wcsrchr(path, L'.');
297
+ if (ext) {
298
+ if (_wcsicmp(ext, L".cpp") == 0 || _wcsicmp(ext, L".hpp") == 0 ||
299
+ _wcsicmp(ext, L".cc") == 0 || _wcsicmp(ext, L".cxx") == 0)
300
+ lang = "cpp";
301
+ }
302
+
303
+ /* Escape text into JSON string */
304
+ char *esc = (char *)malloc(len * 2 + 8);
305
+ if (!esc) return SOV_ERR_ALLOC;
306
+ json_escape(text, esc, (int)(len * 2 + 8));
307
+
308
+ char *params = (char *)malloc(len * 2 + 512);
309
+ if (!params) { free(esc); return SOV_ERR_ALLOC; }
310
+ snprintf(params, len * 2 + 512,
311
+ "{\"textDocument\":{"
312
+ "\"uri\":\"%s\","
313
+ "\"languageId\":\"%s\","
314
+ "\"version\":1,"
315
+ "\"text\":\"%s\""
316
+ "}}",
317
+ uri, lang, esc);
318
+
319
+ SovResult r = lsp_notify(c, "textDocument/didOpen", params);
320
+ free(esc);
321
+ free(params);
322
+ return r;
323
+ }
324
+
325
+ SovResult lsp_client_change(LspClient *c, const wchar_t *path,
326
+ const char *text, size_t len, int version) {
327
+ if (!c->alive) return SOV_ERR_IO;
328
+
329
+ char uri[MAX_PATH * 3];
330
+ path_to_uri(path, uri, sizeof(uri));
331
+
332
+ char *esc = (char *)malloc(len * 2 + 8);
333
+ if (!esc) return SOV_ERR_ALLOC;
334
+ json_escape(text, esc, (int)(len * 2 + 8));
335
+
336
+ char *params = (char *)malloc(len * 2 + 512);
337
+ if (!params) { free(esc); return SOV_ERR_ALLOC; }
338
+ snprintf(params, len * 2 + 512,
339
+ "{\"textDocument\":{"
340
+ "\"uri\":\"%s\","
341
+ "\"version\":%d"
342
+ "},"
343
+ "\"contentChanges\":[{\"text\":\"%s\"}]}",
344
+ uri, version, esc);
345
+
346
+ SovResult r = lsp_notify(c, "textDocument/didChange", params);
347
+ free(esc);
348
+ free(params);
349
+ return r;
350
+ }
351
+
352
+ void lsp_client_tick(LspClient *c) {
353
+ if (!c->alive) return;
354
+
355
+ /* Non-blocking drain of stdout pipe */
356
+ DWORD avail = 0;
357
+ while (PeekNamedPipe(c->pipe_stdout, NULL, 0, NULL, &avail, NULL) && avail > 0) {
358
+ int space = (int)sizeof(c->rbuf) - c->rbuf_len - 1;
359
+ if (space <= 0) { c->rbuf_len = 0; break; } /* overflow: reset */
360
+ DWORD to_read = (avail < (DWORD)space) ? avail : (DWORD)space;
361
+ DWORD nread = 0;
362
+ if (!ReadFile(c->pipe_stdout, c->rbuf + c->rbuf_len, to_read, &nread, NULL))
363
+ break;
364
+ c->rbuf_len += (int)nread;
365
+ c->rbuf[c->rbuf_len] = '\0';
366
+ }
367
+
368
+ /* Parse complete Content-Length frames */
369
+ char *p = c->rbuf;
370
+ int remaining = c->rbuf_len;
371
+
372
+ while (remaining > 0) {
373
+ /* Look for "Content-Length: N\r\n\r\n" */
374
+ /* strnstr not in MinGW — manual search with length guard */
375
+ char *hdr = NULL;
376
+ for (int si = 0; si <= remaining - 16; si++) {
377
+ if (memcmp(p + si, "Content-Length: ", 16) == 0) {
378
+ hdr = p + si; break;
379
+ }
380
+ }
381
+ if (!hdr) break;
382
+
383
+ int content_len = atoi(hdr + 16);
384
+ if (content_len <= 0) { remaining = 0; break; }
385
+
386
+ /* Find end of header (\r\n\r\n) */
387
+ char *body = strstr(hdr, "\r\n\r\n");
388
+ if (!body) break;
389
+ body += 4;
390
+
391
+ int consumed = (int)(body - p);
392
+ int body_avail = remaining - consumed;
393
+ if (body_avail < content_len) break; /* incomplete body, wait */
394
+
395
+ /* Process this message */
396
+ body[content_len] = '\0';
397
+ dispatch_message(c, body);
398
+
399
+ p = body + content_len;
400
+ remaining = remaining - consumed - content_len;
401
+ }
402
+
403
+ /* Compact buffer */
404
+ if (remaining > 0 && p != c->rbuf)
405
+ memmove(c->rbuf, p, (size_t)remaining);
406
+ c->rbuf_len = remaining;
407
+ }
408
+
409
+ void lsp_client_shutdown(LspClient *c) {
410
+ if (!c) return;
411
+ if (c->alive) {
412
+ lsp_request(c, "shutdown", NULL);
413
+ lsp_notify(c, "exit", NULL);
414
+ WaitForSingleObject(c->pi.hProcess, 2000);
415
+ TerminateProcess(c->pi.hProcess, 0);
416
+ CloseHandle(c->pi.hProcess);
417
+ CloseHandle(c->pi.hThread);
418
+ }
419
+ CloseHandle(c->pipe_stdin);
420
+ CloseHandle(c->pipe_stdout);
421
+ free(c);
422
+ }
ide/native/lsp/client.h ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_LSP_CLIENT_H
2
+ #define SOVEREIGN_LSP_CLIENT_H
3
+
4
+ #include <windows.h>
5
+ #include <stdbool.h>
6
+ #include <stdint.h>
7
+ #include "../core/errors.h"
8
+
9
+ typedef struct LspClient LspClient;
10
+
11
+ typedef struct LspDiagnostic {
12
+ wchar_t file[MAX_PATH];
13
+ uint32_t line; /* 0-based */
14
+ uint32_t col; /* 0-based */
15
+ uint32_t severity; /* 1=error 2=warning 3=info 4=hint */
16
+ char message[512];
17
+ } LspDiagnostic;
18
+
19
+ #define LSP_MAX_DIAG 256
20
+
21
+ typedef struct LspDiagList {
22
+ LspDiagnostic entries[LSP_MAX_DIAG];
23
+ int count;
24
+ wchar_t uri[MAX_PATH]; /* file these diags belong to */
25
+ } LspDiagList;
26
+
27
+ /* Called on UI thread after each publishDiagnostics notification */
28
+ typedef void (*lsp_diag_cb)(const LspDiagList *diags, void *ctx);
29
+
30
+ SovResult lsp_client_start(LspClient **out, const wchar_t *server_cmd,
31
+ lsp_diag_cb on_diag, void *cb_ctx);
32
+ SovResult lsp_client_initialize(LspClient *c, const wchar_t *root_path);
33
+ SovResult lsp_client_open(LspClient *c, const wchar_t *path,
34
+ const char *text, size_t len);
35
+ SovResult lsp_client_change(LspClient *c, const wchar_t *path,
36
+ const char *text, size_t len, int version);
37
+ void lsp_client_tick(LspClient *c); /* pump: call from message loop */
38
+ void lsp_client_shutdown(LspClient *c);
39
+
40
+ #endif
ide/native/main.c ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Entry Point
3
+ * Win32 wWinMain: init → window → message loop → shutdown.
4
+ */
5
+
6
+ #include "platform/windows/application.h"
7
+ #include "platform/windows/shell.h"
8
+ #include "core/errors.h"
9
+
10
+ #define INITIAL_WIDTH 1280
11
+ #define INITIAL_HEIGHT 800
12
+
13
+ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPWSTR cmdLine, int nCmdShow) {
14
+ (void)hPrev; (void)cmdLine; (void)nCmdShow;
15
+
16
+ app_init(hInstance);
17
+
18
+ SovResult r = shell_create(hInstance, INITIAL_WIDTH, INITIAL_HEIGHT);
19
+ SOV_ASSERT(r == SOV_OK, "Failed to create shell window");
20
+
21
+ int exit_code = app_run();
22
+
23
+ app_shutdown();
24
+ return exit_code;
25
+ }
ide/native/platform/windows/application.c ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Win32 Application Entry
3
+ * Owns the message loop, DPI awareness, and top-level window creation.
4
+ */
5
+
6
+ #include "application.h"
7
+ #include "window.h"
8
+ #include "../../core/events.h"
9
+ #include "../../core/arena.h"
10
+ #include "../../core/strings.h"
11
+
12
+ #include <shellscalingapi.h>
13
+
14
+ static struct {
15
+ HINSTANCE hinstance;
16
+ bool running;
17
+ Arena frame_arena;
18
+ } g_app;
19
+
20
+ static void app_set_dpi_awareness(void) {
21
+ SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
22
+ }
23
+
24
+ void app_init(HINSTANCE hinstance) {
25
+ g_app.hinstance = hinstance;
26
+ g_app.running = true;
27
+
28
+ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
29
+ app_set_dpi_awareness();
30
+ arena_init_default(&g_app.frame_arena);
31
+ strings_init();
32
+ events_init();
33
+ }
34
+
35
+ int app_run(void) {
36
+ MSG msg = {0};
37
+ while (g_app.running) {
38
+ while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
39
+ if (msg.message == WM_QUIT) {
40
+ g_app.running = false;
41
+ break;
42
+ }
43
+ TranslateMessage(&msg);
44
+ DispatchMessageW(&msg);
45
+ }
46
+
47
+ arena_reset(&g_app.frame_arena);
48
+ }
49
+
50
+ return (int)msg.wParam;
51
+ }
52
+
53
+ void app_request_quit(void) {
54
+ PostQuitMessage(0);
55
+ g_app.running = false;
56
+ }
57
+
58
+ void app_shutdown(void) {
59
+ strings_shutdown();
60
+ arena_destroy(&g_app.frame_arena);
61
+ CoUninitialize();
62
+ }
63
+
64
+ HINSTANCE app_get_hinstance(void) {
65
+ return g_app.hinstance;
66
+ }
67
+
68
+ Arena *app_frame_arena(void) {
69
+ return &g_app.frame_arena;
70
+ }
ide/native/platform/windows/application.h ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_APPLICATION_H
2
+ #define SOVEREIGN_APPLICATION_H
3
+
4
+ #include <windows.h>
5
+ #include <stdbool.h>
6
+ #include "../../core/arena.h"
7
+
8
+ void app_init(HINSTANCE hinstance);
9
+ int app_run(void);
10
+ void app_request_quit(void);
11
+ void app_shutdown(void);
12
+ HINSTANCE app_get_hinstance(void);
13
+ Arena *app_frame_arena(void);
14
+
15
+ #endif /* SOVEREIGN_APPLICATION_H */
ide/native/platform/windows/shell.c ADDED
@@ -0,0 +1,681 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Application Shell
3
+ * Top-level window + child panel HWNDs + layout engine.
4
+ * This replaces window.c as the orchestrator.
5
+ */
6
+
7
+ #include "shell.h"
8
+ #include "application.h"
9
+ #include "../../ui/layout.h"
10
+ #include "../../ui/project_tree.h"
11
+ #include "../../ui/status_bar.h"
12
+ #include "../../ui/output_panel.h"
13
+ #include "../../editor/editor_view.h"
14
+ #include "../../editor/document.h"
15
+ #include "../../terminal/terminal_view.h"
16
+ #include "../../git/repository.h"
17
+ #include "../../build/cmake_runner.h"
18
+ #include "../../lsp/client.h"
19
+ #include "../../core/events.h"
20
+ #include <commdlg.h>
21
+ #include <shlobj.h>
22
+
23
+ #define TIMER_CARET 1
24
+ #define TIMER_PTY 2
25
+ #define TIMER_GIT 3
26
+
27
+ /* Child window IDs */
28
+ #define IDC_SIDEBAR 101
29
+ #define IDC_EDITOR 102
30
+ #define IDC_BOTTOM 103
31
+ #define IDC_STATUSBAR 104
32
+
33
+ #define SHELL_CLASS L"SovShell"
34
+ #define PANEL_CLASS L"SovPanel"
35
+ #define SHELL_TITLE L"Sovereign IDE"
36
+
37
+ static struct {
38
+ HWND frame;
39
+ HWND wnd_sidebar;
40
+ HWND wnd_editor;
41
+ HWND wnd_bottom; /* container only — no D2D on this one */
42
+ HWND wnd_output; /* OutputPanel lives here */
43
+ HWND wnd_terminal; /* TerminalView lives here */
44
+ HWND wnd_status;
45
+
46
+ Layout layout;
47
+ ProjectTree *tree;
48
+ EditorView *editor;
49
+ OutputPanel *output;
50
+ TerminalView *terminal;
51
+ StatusBar *status;
52
+
53
+ GitRepo git;
54
+ CmakeRunner *builder;
55
+ LspClient *lsp;
56
+ int lsp_version;
57
+
58
+ Document doc;
59
+ int client_w;
60
+ int client_h;
61
+ bool terminal_focused;
62
+ } g_shell;
63
+
64
+ /* -----------------------------------------------------------
65
+ * Build runner callbacks
66
+ * ----------------------------------------------------------- */
67
+ static void on_build_line(const char *text, bool is_err, void *ctx) {
68
+ (void)ctx; (void)is_err;
69
+ if (!g_shell.output) return;
70
+ /* Convert UTF-8 line to wide */
71
+ wchar_t wbuf[2048];
72
+ MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf, 2048);
73
+ output_panel_append(g_shell.output, wbuf);
74
+ output_panel_append(g_shell.output, L"\n");
75
+ /* Show output panel while building */
76
+ g_shell.terminal_focused = false;
77
+ ShowWindow(g_shell.wnd_output, SW_SHOW);
78
+ ShowWindow(g_shell.wnd_terminal, SW_HIDE);
79
+ }
80
+
81
+ static void on_build_done(int exit_code, void *ctx) {
82
+ (void)ctx;
83
+ if (!g_shell.output) return;
84
+ wchar_t msg[64];
85
+ if (exit_code == 0)
86
+ swprintf_s(msg, 64, L"✔ Build succeeded\n");
87
+ else
88
+ swprintf_s(msg, 64, L"✘ Build failed (exit %d)\n", exit_code);
89
+ output_panel_append(g_shell.output, msg);
90
+ if (g_shell.status)
91
+ status_bar_set_message(g_shell.status,
92
+ exit_code == 0 ? L"Build OK" : L"Build FAILED");
93
+ InvalidateRect(g_shell.wnd_bottom, NULL, FALSE);
94
+ InvalidateRect(g_shell.wnd_status, NULL, FALSE);
95
+ }
96
+
97
+ /* -----------------------------------------------------------
98
+ * LSP diagnostics callback
99
+ * ----------------------------------------------------------- */
100
+ static void on_lsp_diag(const LspDiagList *list, void *ctx) {
101
+ (void)ctx;
102
+ if (!g_shell.editor) return;
103
+ /* Only apply diags that match the current open file */
104
+ if (g_shell.doc.has_path &&
105
+ _wcsicmp(list->uri, g_shell.doc.path) == 0)
106
+ {
107
+ editor_view_set_diagnostics(g_shell.editor, list);
108
+ }
109
+ }
110
+
111
+ /* -----------------------------------------------------------
112
+ * File open callback (from project tree click)
113
+ * ----------------------------------------------------------- */
114
+ static void on_open_file(const wchar_t *path, void *ctx) {
115
+ (void)ctx;
116
+ document_destroy(&g_shell.doc);
117
+ if (document_open(&g_shell.doc, path) == SOV_OK) {
118
+ editor_view_set_document(g_shell.editor, &g_shell.doc);
119
+ editor_view_set_diagnostics(g_shell.editor, NULL);
120
+
121
+ /* Notify LSP of the new file */
122
+ if (g_shell.lsp) {
123
+ char utf8[1 << 20];
124
+ size_t blen = buffer_length(g_shell.doc.buffer);
125
+ if (blen > sizeof(utf8) - 1) blen = sizeof(utf8) - 1;
126
+ buffer_read(g_shell.doc.buffer, 0, utf8, blen);
127
+ utf8[blen] = '\0';
128
+ g_shell.lsp_version = 1;
129
+ lsp_client_open(g_shell.lsp, path, utf8, blen);
130
+ }
131
+
132
+ /* extract filename for status bar */
133
+ const wchar_t *name = wcsrchr(path, L'\\');
134
+ status_bar_set_file(g_shell.status, name ? name + 1 : path, false);
135
+
136
+ output_panel_append(g_shell.output, L"Opened: ");
137
+ output_panel_append(g_shell.output, path);
138
+ output_panel_append(g_shell.output, L"\n");
139
+ } else {
140
+ output_panel_append(g_shell.output, L"Failed to open file\n");
141
+ }
142
+ InvalidateRect(g_shell.wnd_editor, NULL, FALSE);
143
+ }
144
+
145
+ /* -----------------------------------------------------------
146
+ * Reposition all child windows from layout
147
+ * ----------------------------------------------------------- */
148
+ static void apply_layout(void) {
149
+ PanelRect rects[PANEL_COUNT];
150
+ layout_compute(&g_shell.layout, g_shell.client_w, g_shell.client_h, rects);
151
+
152
+ SetWindowPos(g_shell.wnd_sidebar, NULL,
153
+ rects[PANEL_SIDEBAR].x, rects[PANEL_SIDEBAR].y,
154
+ rects[PANEL_SIDEBAR].w, rects[PANEL_SIDEBAR].h,
155
+ SWP_NOZORDER | SWP_NOACTIVATE);
156
+
157
+ SetWindowPos(g_shell.wnd_editor, NULL,
158
+ rects[PANEL_EDITOR].x, rects[PANEL_EDITOR].y,
159
+ rects[PANEL_EDITOR].w, rects[PANEL_EDITOR].h,
160
+ SWP_NOZORDER | SWP_NOACTIVATE);
161
+
162
+ SetWindowPos(g_shell.wnd_bottom, NULL,
163
+ rects[PANEL_BOTTOM].x, rects[PANEL_BOTTOM].y,
164
+ rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h,
165
+ SWP_NOZORDER | SWP_NOACTIVATE);
166
+
167
+ SetWindowPos(g_shell.wnd_status, NULL,
168
+ rects[PANEL_STATUSBAR].x, rects[PANEL_STATUSBAR].y,
169
+ rects[PANEL_STATUSBAR].w, rects[PANEL_STATUSBAR].h,
170
+ SWP_NOZORDER | SWP_NOACTIVATE);
171
+
172
+ if (g_shell.editor)
173
+ editor_view_resize(g_shell.editor,
174
+ (uint32_t)rects[PANEL_EDITOR].w, (uint32_t)rects[PANEL_EDITOR].h);
175
+ if (g_shell.tree)
176
+ project_tree_resize(g_shell.tree,
177
+ rects[PANEL_SIDEBAR].w, rects[PANEL_SIDEBAR].h);
178
+ /* Resize output+terminal sub-windows to fill wnd_bottom */
179
+ if (g_shell.wnd_output)
180
+ SetWindowPos(g_shell.wnd_output, NULL, 0, 0,
181
+ rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h, SWP_NOZORDER | SWP_NOACTIVATE);
182
+ if (g_shell.wnd_terminal)
183
+ SetWindowPos(g_shell.wnd_terminal, NULL, 0, 0,
184
+ rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h, SWP_NOZORDER | SWP_NOACTIVATE);
185
+ if (g_shell.output)
186
+ output_panel_resize(g_shell.output,
187
+ rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h);
188
+ if (g_shell.terminal)
189
+ terminal_view_resize(g_shell.terminal,
190
+ rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h);
191
+ if (g_shell.status)
192
+ status_bar_resize(g_shell.status,
193
+ rects[PANEL_STATUSBAR].w, rects[PANEL_STATUSBAR].h);
194
+ }
195
+
196
+ /* -----------------------------------------------------------
197
+ * Panel child window proc (sidebar / editor / bottom / status)
198
+ * ----------------------------------------------------------- */
199
+ static LRESULT CALLBACK panel_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
200
+ switch (msg) {
201
+ case WM_PAINT: {
202
+ PAINTSTRUCT ps;
203
+ BeginPaint(hwnd, &ps);
204
+ if (hwnd == g_shell.wnd_sidebar && g_shell.tree)
205
+ project_tree_paint(g_shell.tree);
206
+ else if (hwnd == g_shell.wnd_editor && g_shell.editor) {
207
+ RECT rc; GetClientRect(hwnd, &rc);
208
+ editor_view_paint(g_shell.editor, rc);
209
+ }
210
+ else if (hwnd == g_shell.wnd_output && g_shell.output)
211
+ output_panel_paint(g_shell.output);
212
+ else if (hwnd == g_shell.wnd_terminal && g_shell.terminal)
213
+ terminal_view_paint(g_shell.terminal);
214
+ else if (hwnd == g_shell.wnd_status && g_shell.status)
215
+ status_bar_paint(g_shell.status);
216
+ EndPaint(hwnd, &ps);
217
+ return 0;
218
+ }
219
+
220
+ case WM_LBUTTONDOWN: {
221
+ int x = (int)(short)LOWORD(lp), y = (int)(short)HIWORD(lp);
222
+ if (hwnd == g_shell.wnd_sidebar && g_shell.tree)
223
+ project_tree_mouse_down(g_shell.tree, x, y);
224
+ else if (hwnd == g_shell.wnd_editor && g_shell.editor) {
225
+ SetFocus(hwnd);
226
+ g_shell.terminal_focused = false;
227
+ editor_view_mouse_down(g_shell.editor, x, y);
228
+ }
229
+ else if (hwnd == g_shell.wnd_terminal || hwnd == g_shell.wnd_output) {
230
+ SetFocus(hwnd);
231
+ g_shell.terminal_focused = (hwnd == g_shell.wnd_terminal);
232
+ }
233
+ return 0;
234
+ }
235
+
236
+ case WM_MOUSEMOVE: {
237
+ if (hwnd == g_shell.wnd_editor && g_shell.editor) {
238
+ bool btn = (wp & MK_LBUTTON) != 0;
239
+ editor_view_mouse_move(g_shell.editor,
240
+ (int)(short)LOWORD(lp), (int)(short)HIWORD(lp), btn);
241
+ }
242
+ return 0;
243
+ }
244
+
245
+ case WM_KEYDOWN:
246
+ case WM_SYSKEYDOWN: {
247
+ if (hwnd == g_shell.wnd_terminal && g_shell.terminal) {
248
+ UINT mods = 0;
249
+ if (GetKeyState(VK_CONTROL) & 0x8000) mods |= 2;
250
+ terminal_view_key_down(g_shell.terminal, (UINT)wp, mods);
251
+ return 0;
252
+ }
253
+ if (hwnd == g_shell.wnd_editor && g_shell.editor) {
254
+ UINT mods = 0;
255
+ if (GetKeyState(VK_SHIFT) & 0x8000) mods |= 1;
256
+ if (GetKeyState(VK_CONTROL) & 0x8000) mods |= 2;
257
+ if (GetKeyState(VK_MENU) & 0x8000) mods |= 4;
258
+ editor_view_key_down(g_shell.editor, (UINT)wp, mods);
259
+
260
+ /* Update status bar line/col after navigation */
261
+ if (g_shell.status && g_shell.doc.buffer) {
262
+ size_t off = editor_view_caret_offset(g_shell.editor);
263
+ char tmp[1];
264
+ /* count lines up to offset */
265
+ size_t line = 1, col = 1;
266
+ for (size_t i = 0; i < off; i++) {
267
+ size_t n = buffer_read(g_shell.doc.buffer, i, tmp, 1);
268
+ if (n && tmp[0] == '\n') { line++; col = 1; } else col++;
269
+ }
270
+ status_bar_set_pos(g_shell.status, (int)line, (int)col);
271
+ status_bar_set_file(g_shell.status,
272
+ g_shell.doc.has_path ? wcsrchr(g_shell.doc.path, L'\\') + 1 : L"untitled",
273
+ document_is_dirty(&g_shell.doc));
274
+ }
275
+ } else if (hwnd == g_shell.wnd_sidebar && g_shell.tree) {
276
+ project_tree_key_down(g_shell.tree, (UINT)wp);
277
+ }
278
+ return 0;
279
+ }
280
+
281
+ case WM_CHAR: {
282
+ if (hwnd == g_shell.wnd_terminal && g_shell.terminal) {
283
+ terminal_view_char(g_shell.terminal, (wchar_t)wp);
284
+ } else if (hwnd == g_shell.wnd_editor && g_shell.editor) {
285
+ editor_view_char(g_shell.editor, (wchar_t)wp);
286
+ /* Notify LSP of change */
287
+ if (g_shell.lsp && g_shell.doc.has_path && g_shell.doc.buffer) {
288
+ char utf8[1 << 20];
289
+ size_t blen = buffer_length(g_shell.doc.buffer);
290
+ if (blen < sizeof(utf8)) {
291
+ buffer_read(g_shell.doc.buffer, 0, utf8, blen);
292
+ utf8[blen] = '\0';
293
+ lsp_client_change(g_shell.lsp, g_shell.doc.path,
294
+ utf8, blen, ++g_shell.lsp_version);
295
+ }
296
+ }
297
+ }
298
+ return 0;
299
+ }
300
+
301
+ case WM_MOUSEWHEEL: {
302
+ if (hwnd == g_shell.wnd_bottom && g_shell.output)
303
+ output_panel_scroll(g_shell.output, GET_WHEEL_DELTA_WPARAM(wp) / 40);
304
+ return 0;
305
+ }
306
+
307
+ case WM_TIMER:
308
+ if (wp == TIMER_PTY && hwnd == g_shell.wnd_terminal) {
309
+ terminal_view_tick(g_shell.terminal);
310
+ InvalidateRect(g_shell.wnd_terminal, NULL, FALSE);
311
+ } else {
312
+ InvalidateRect(hwnd, NULL, FALSE);
313
+ }
314
+ return 0;
315
+ }
316
+
317
+ return DefWindowProcW(hwnd, msg, wp, lp);
318
+ }
319
+
320
+ /* -----------------------------------------------------------
321
+ * Frame window proc (top-level)
322
+ * ----------------------------------------------------------- */
323
+ static LRESULT CALLBACK shell_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
324
+ switch (msg) {
325
+
326
+ case WM_CREATE: {
327
+ layout_init(&g_shell.layout);
328
+
329
+ /* Measure initial client area so D2D render targets get real dimensions */
330
+ RECT cr;
331
+ GetClientRect(hwnd, &cr);
332
+ g_shell.client_w = cr.right - cr.left;
333
+ g_shell.client_h = cr.bottom - cr.top;
334
+ if (g_shell.client_w < 1) g_shell.client_w = 1280;
335
+ if (g_shell.client_h < 1) g_shell.client_h = 800;
336
+
337
+ /* Compute layout so child panels get non-zero sizes */
338
+ PanelRect rects[PANEL_COUNT];
339
+ layout_compute(&g_shell.layout, g_shell.client_w, g_shell.client_h, rects);
340
+
341
+ /* Create child panels at their real positions */
342
+ HINSTANCE hi = app_get_hinstance();
343
+ g_shell.wnd_sidebar = CreateWindowExW(0, PANEL_CLASS, NULL,
344
+ WS_CHILD | WS_VISIBLE,
345
+ rects[PANEL_SIDEBAR].x, rects[PANEL_SIDEBAR].y,
346
+ rects[PANEL_SIDEBAR].w, rects[PANEL_SIDEBAR].h,
347
+ hwnd, (HMENU)IDC_SIDEBAR, hi, NULL);
348
+ g_shell.wnd_editor = CreateWindowExW(0, PANEL_CLASS, NULL,
349
+ WS_CHILD | WS_VISIBLE,
350
+ rects[PANEL_EDITOR].x, rects[PANEL_EDITOR].y,
351
+ rects[PANEL_EDITOR].w, rects[PANEL_EDITOR].h,
352
+ hwnd, (HMENU)IDC_EDITOR, hi, NULL);
353
+ g_shell.wnd_bottom = CreateWindowExW(0, PANEL_CLASS, NULL,
354
+ WS_CHILD | WS_VISIBLE,
355
+ rects[PANEL_BOTTOM].x, rects[PANEL_BOTTOM].y,
356
+ rects[PANEL_BOTTOM].w, rects[PANEL_BOTTOM].h,
357
+ hwnd, (HMENU)IDC_BOTTOM, hi, NULL);
358
+ g_shell.wnd_status = CreateWindowExW(0, PANEL_CLASS, NULL,
359
+ WS_CHILD | WS_VISIBLE,
360
+ rects[PANEL_STATUSBAR].x, rects[PANEL_STATUSBAR].y,
361
+ rects[PANEL_STATUSBAR].w, rects[PANEL_STATUSBAR].h,
362
+ hwnd, (HMENU)IDC_STATUSBAR, hi, NULL);
363
+
364
+ /* Output and terminal need separate HWNDs (each owns one D2D RT) */
365
+ int bw = rects[PANEL_BOTTOM].w;
366
+ int bh = rects[PANEL_BOTTOM].h;
367
+ g_shell.wnd_output = CreateWindowExW(0, PANEL_CLASS, NULL,
368
+ WS_CHILD | WS_VISIBLE, 0, 0, bw, bh,
369
+ g_shell.wnd_bottom, NULL, hi, NULL);
370
+ g_shell.wnd_terminal = CreateWindowExW(0, PANEL_CLASS, NULL,
371
+ WS_CHILD, 0, 0, bw, bh,
372
+ g_shell.wnd_bottom, NULL, hi, NULL);
373
+
374
+ /* Init subsystems — each on its own HWND */
375
+ #define DBGINIT(call, name) do { \
376
+ SovResult _r = (call); \
377
+ if (_r != SOV_OK) MessageBoxW(NULL, L##name L" FAILED", L"Init Error", MB_OK); \
378
+ } while(0)
379
+ project_tree_create(&g_shell.tree, g_shell.wnd_sidebar);
380
+ editor_view_create(&g_shell.editor, g_shell.wnd_editor);
381
+ output_panel_create(&g_shell.output, g_shell.wnd_output);
382
+ terminal_view_create(&g_shell.terminal, g_shell.wnd_terminal);
383
+ status_bar_create(&g_shell.status, g_shell.wnd_status);
384
+ #undef DBGINIT
385
+ g_shell.terminal_focused = false; /* default: show output panel */
386
+ /* wnd_output visible, wnd_terminal hidden until user toggles */
387
+ ShowWindow(g_shell.wnd_output, SW_SHOW);
388
+ ShowWindow(g_shell.wnd_terminal, SW_HIDE);
389
+
390
+ /* Wire callbacks */
391
+ if (g_shell.tree) {
392
+ project_tree_set_callback(g_shell.tree, on_open_file, NULL);
393
+ }
394
+
395
+ /* Default document */
396
+ document_new(&g_shell.doc);
397
+ buffer_insert(g_shell.doc.buffer, 0,
398
+ "#include <stdio.h>\n\nint main(void) {\n printf(\"Sovereign IDE\\n\");\n return 0;\n}\n",
399
+ 88);
400
+ buffer_mark_clean(g_shell.doc.buffer);
401
+ if (g_shell.editor)
402
+ editor_view_set_document(g_shell.editor, &g_shell.doc);
403
+
404
+ /* Open current working directory in project tree */
405
+ wchar_t cwd[MAX_PATH];
406
+ GetCurrentDirectoryW(MAX_PATH, cwd);
407
+ if (g_shell.tree)
408
+ project_tree_open(g_shell.tree, cwd);
409
+
410
+ if (g_shell.status) {
411
+ status_bar_set_file(g_shell.status, L"main.c", false);
412
+ status_bar_set_pos(g_shell.status, 1, 1);
413
+ status_bar_set_branch(g_shell.status, L"master");
414
+ status_bar_set_message(g_shell.status, L"GCC 13.2");
415
+ }
416
+
417
+ /* Output welcome */
418
+ if (g_shell.output) {
419
+ output_panel_append(g_shell.output, L"Sovereign IDE ready.\n");
420
+ output_panel_append(g_shell.output, L"Project: ");
421
+ output_panel_append(g_shell.output, cwd);
422
+ output_panel_append(g_shell.output, L"\n");
423
+ }
424
+
425
+ /* Git branch detection */
426
+ if (git_repo_open(&g_shell.git, cwd) == SOV_OK && g_shell.status) {
427
+ wchar_t branch_disp[136];
428
+ swprintf_s(branch_disp, 136, L"%s%s",
429
+ g_shell.git.detached ? L"detached:" : L"",
430
+ g_shell.git.branch);
431
+ status_bar_set_branch(g_shell.status, branch_disp);
432
+ }
433
+
434
+ /* CMake build runner */
435
+ wchar_t build_dir[MAX_PATH];
436
+ swprintf_s(build_dir, MAX_PATH, L"%s\\build", cwd);
437
+ cmake_runner_create(&g_shell.builder, build_dir,
438
+ on_build_line, on_build_done, NULL);
439
+
440
+ /* Start LSP server (clangd must be on PATH) */
441
+ g_shell.lsp = NULL;
442
+ g_shell.lsp_version = 1;
443
+ if (lsp_client_start(&g_shell.lsp, L"clangd",
444
+ on_lsp_diag, NULL) == SOV_OK) {
445
+ lsp_client_initialize(g_shell.lsp, cwd);
446
+ output_panel_append(g_shell.output, L"LSP: clangd started.\n");
447
+ } else {
448
+ output_panel_append(g_shell.output, L"LSP: clangd not found — install LLVM.\n");
449
+ g_shell.lsp = NULL;
450
+ }
451
+
452
+ SetTimer(g_shell.wnd_editor, TIMER_CARET, 530, NULL);
453
+ SetTimer(g_shell.wnd_terminal, TIMER_PTY, 16, NULL); /* ~60fps PTY poll */
454
+ SetTimer(hwnd, TIMER_GIT, 3000, NULL); /* git refresh every 3s */
455
+ return 0;
456
+ }
457
+
458
+ case WM_SIZE: {
459
+ g_shell.client_w = (int)(short)LOWORD(lp);
460
+ g_shell.client_h = (int)(short)HIWORD(lp);
461
+ apply_layout();
462
+ return 0;
463
+ }
464
+
465
+ case WM_MOUSEMOVE: {
466
+ int mx = (int)(short)LOWORD(lp);
467
+ int my = (int)(short)HIWORD(lp);
468
+ if (g_shell.layout.dragging) {
469
+ layout_update_drag(&g_shell.layout, mx, my);
470
+ apply_layout();
471
+ return 0;
472
+ }
473
+ int spl = layout_hit_splitter(&g_shell.layout, g_shell.client_w, g_shell.client_h, mx, my);
474
+ if (spl == 0 || spl == 1)
475
+ SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_SIZEWE));
476
+ else if (spl == 2)
477
+ SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_SIZENS));
478
+ else
479
+ SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
480
+ return 0;
481
+ }
482
+
483
+ case WM_LBUTTONDOWN: {
484
+ int mx = (int)(short)LOWORD(lp);
485
+ int my = (int)(short)HIWORD(lp);
486
+ int spl = layout_hit_splitter(&g_shell.layout, g_shell.client_w, g_shell.client_h, mx, my);
487
+ if (spl >= 0) {
488
+ layout_begin_drag(&g_shell.layout, spl, mx, my);
489
+ SetCapture(hwnd);
490
+ }
491
+ return 0;
492
+ }
493
+
494
+ case WM_LBUTTONUP:
495
+ layout_end_drag(&g_shell.layout);
496
+ ReleaseCapture();
497
+ return 0;
498
+
499
+ case WM_COMMAND: {
500
+ switch (LOWORD(wp)) {
501
+ case 1001: /* File > Open Folder */
502
+ {
503
+ /* Use SHBrowseForFolder for folder selection */
504
+ BROWSEINFOW bi = {0};
505
+ bi.hwndOwner = hwnd;
506
+ bi.lpszTitle = L"Open Project Folder";
507
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
508
+ LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
509
+ if (pidl) {
510
+ wchar_t path[MAX_PATH];
511
+ SHGetPathFromIDListW(pidl, path);
512
+ CoTaskMemFree(pidl);
513
+ if (g_shell.tree) project_tree_open(g_shell.tree, path);
514
+ output_panel_append(g_shell.output, L"Opened folder: ");
515
+ output_panel_append(g_shell.output, path);
516
+ output_panel_append(g_shell.output, L"\n");
517
+ }
518
+ break;
519
+ }
520
+ case 1002: /* File > Open File */
521
+ {
522
+ OPENFILENAMEW ofn = {0};
523
+ wchar_t file[MAX_PATH] = {0};
524
+ ofn.lStructSize = sizeof(ofn);
525
+ ofn.hwndOwner = hwnd;
526
+ ofn.lpstrFile = file;
527
+ ofn.nMaxFile = MAX_PATH;
528
+ ofn.lpstrFilter = L"C/C++ Files\0*.c;*.cpp;*.h;*.hpp\0All Files\0*.*\0";
529
+ ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
530
+ if (GetOpenFileNameW(&ofn)) {
531
+ on_open_file(file, NULL);
532
+ }
533
+ break;
534
+ }
535
+ case 1003: /* File > Save */
536
+ if (g_shell.doc.has_path) {
537
+ document_save(&g_shell.doc);
538
+ output_panel_append(g_shell.output, L"Saved.\n");
539
+ }
540
+ break;
541
+ case 1010: /* View > Toggle Sidebar */
542
+ g_shell.layout.sidebar_visible = !g_shell.layout.sidebar_visible;
543
+ apply_layout();
544
+ break;
545
+ case 1011: /* View > Toggle Bottom */
546
+ g_shell.layout.bottom_visible = !g_shell.layout.bottom_visible;
547
+ apply_layout();
548
+ break;
549
+ case 1020: /* Run > Build (Ctrl+B) */
550
+ if (g_shell.builder && !cmake_runner_is_running(g_shell.builder)) {
551
+ output_panel_clear(g_shell.output);
552
+ output_panel_append(g_shell.output, L"Building...\n");
553
+ g_shell.terminal_focused = false;
554
+ ShowWindow(g_shell.wnd_output, SW_SHOW);
555
+ ShowWindow(g_shell.wnd_terminal, SW_HIDE);
556
+ if (cmake_runner_build(g_shell.builder) != SOV_OK)
557
+ output_panel_append(g_shell.output, L"cmake not found — is it on PATH?\n");
558
+ }
559
+ break;
560
+ case 1021: /* View > Toggle Terminal/Output */
561
+ g_shell.terminal_focused = !g_shell.terminal_focused;
562
+ ShowWindow(g_shell.wnd_output, g_shell.terminal_focused ? SW_HIDE : SW_SHOW);
563
+ ShowWindow(g_shell.wnd_terminal, g_shell.terminal_focused ? SW_SHOW : SW_HIDE);
564
+ break;
565
+ case 9999: /* File > Exit */
566
+ PostMessageW(hwnd, WM_CLOSE, 0, 0);
567
+ break;
568
+ }
569
+ return 0;
570
+ }
571
+
572
+ case WM_TIMER: {
573
+ if (wp == TIMER_GIT) {
574
+ if (git_repo_refresh(&g_shell.git) == SOV_OK && g_shell.status) {
575
+ wchar_t branch_disp[136];
576
+ swprintf_s(branch_disp, 136, L"%s%s",
577
+ g_shell.git.detached ? L"detached:" : L"",
578
+ g_shell.git.branch);
579
+ status_bar_set_branch(g_shell.status, branch_disp);
580
+ InvalidateRect(g_shell.wnd_status, NULL, FALSE);
581
+ }
582
+ if (g_shell.builder) cmake_runner_tick(g_shell.builder);
583
+ if (g_shell.lsp) lsp_client_tick(g_shell.lsp);
584
+ }
585
+ return 0;
586
+ }
587
+
588
+ case WM_DPICHANGED: {
589
+ RECT *r = (RECT *)lp;
590
+ SetWindowPos(hwnd, NULL, r->left, r->top,
591
+ r->right - r->left, r->bottom - r->top,
592
+ SWP_NOZORDER | SWP_NOACTIVATE);
593
+ return 0;
594
+ }
595
+
596
+ case WM_CLOSE:
597
+ KillTimer(hwnd, TIMER_GIT);
598
+ KillTimer(g_shell.wnd_editor, TIMER_CARET);
599
+ KillTimer(g_shell.wnd_terminal, TIMER_PTY);
600
+ if (g_shell.lsp) { lsp_client_shutdown(g_shell.lsp); g_shell.lsp = NULL; }
601
+ if (g_shell.builder) { cmake_runner_destroy(g_shell.builder); g_shell.builder = NULL; }
602
+ if (g_shell.editor) { editor_view_destroy(g_shell.editor); g_shell.editor = NULL; }
603
+ if (g_shell.tree) { project_tree_destroy(g_shell.tree); g_shell.tree = NULL; }
604
+ if (g_shell.terminal) { terminal_view_destroy(g_shell.terminal); g_shell.terminal = NULL; }
605
+ if (g_shell.output) { output_panel_destroy(g_shell.output); g_shell.output = NULL; }
606
+ if (g_shell.status) { status_bar_destroy(g_shell.status); g_shell.status = NULL; }
607
+ document_destroy(&g_shell.doc);
608
+ DestroyWindow(hwnd);
609
+ return 0;
610
+
611
+ case WM_DESTROY:
612
+ app_request_quit();
613
+ PostQuitMessage(0);
614
+ return 0;
615
+ }
616
+
617
+ return DefWindowProcW(hwnd, msg, wp, lp);
618
+ }
619
+
620
+ /* Build the native menu */
621
+ static void build_menu(HWND hwnd) {
622
+ HMENU bar = CreateMenu();
623
+ HMENU file = CreatePopupMenu();
624
+ HMENU view = CreatePopupMenu();
625
+ HMENU run = CreatePopupMenu();
626
+
627
+ AppendMenuW(file, MF_STRING, 1001, L"Open &Folder…\tCtrl+Shift+O");
628
+ AppendMenuW(file, MF_STRING, 1002, L"Open &File…\tCtrl+O");
629
+ AppendMenuW(file, MF_SEPARATOR, 0, NULL);
630
+ AppendMenuW(file, MF_STRING, 1003, L"&Save\tCtrl+S");
631
+ AppendMenuW(file, MF_SEPARATOR, 0, NULL);
632
+ AppendMenuW(file, MF_STRING, 9999, L"E&xit");
633
+
634
+ AppendMenuW(view, MF_STRING, 1010, L"Toggle &Sidebar\tCtrl+\\");
635
+ AppendMenuW(view, MF_STRING, 1011, L"Toggle &Output\tCtrl+J");
636
+ AppendMenuW(view, MF_STRING, 1021, L"Toggle &Terminal\tCtrl+`");
637
+
638
+ AppendMenuW(run, MF_STRING, 1020, L"&Build\tCtrl+B");
639
+
640
+ AppendMenuW(bar, MF_POPUP, (UINT_PTR)file, L"&File");
641
+ AppendMenuW(bar, MF_POPUP, (UINT_PTR)view, L"&View");
642
+ AppendMenuW(bar, MF_POPUP, (UINT_PTR)run, L"&Run");
643
+ SetMenu(hwnd, bar);
644
+ }
645
+
646
+ SovResult shell_create(HINSTANCE hinstance, int width, int height) {
647
+ /* Register panel class */
648
+ WNDCLASSEXW pc = {0};
649
+ pc.cbSize = sizeof(pc);
650
+ pc.style = CS_HREDRAW | CS_VREDRAW;
651
+ pc.lpfnWndProc = panel_proc;
652
+ pc.hInstance = hinstance;
653
+ pc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
654
+ pc.lpszClassName = PANEL_CLASS;
655
+ RegisterClassExW(&pc);
656
+
657
+ /* Register shell class */
658
+ WNDCLASSEXW sc = {0};
659
+ sc.cbSize = sizeof(sc);
660
+ sc.style = CS_HREDRAW | CS_VREDRAW;
661
+ sc.lpfnWndProc = shell_proc;
662
+ sc.hInstance = hinstance;
663
+ sc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
664
+ sc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
665
+ sc.lpszClassName = SHELL_CLASS;
666
+ RegisterClassExW(&sc);
667
+
668
+ g_shell.frame = CreateWindowExW(
669
+ 0, SHELL_CLASS, SHELL_TITLE,
670
+ WS_OVERLAPPEDWINDOW,
671
+ CW_USEDEFAULT, CW_USEDEFAULT, width, height,
672
+ NULL, NULL, hinstance, NULL
673
+ );
674
+
675
+ if (!g_shell.frame) return SOV_ERR_IO;
676
+
677
+ build_menu(g_shell.frame);
678
+ ShowWindow(g_shell.frame, SW_SHOW);
679
+ UpdateWindow(g_shell.frame);
680
+ return SOV_OK;
681
+ }
ide/native/platform/windows/shell.h ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_SHELL_H
2
+ #define SOVEREIGN_SHELL_H
3
+
4
+ #include <windows.h>
5
+ #include "../../core/errors.h"
6
+
7
+ SovResult shell_create(HINSTANCE hinstance, int width, int height);
8
+
9
+ #endif
ide/native/platform/windows/window.c ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — Win32 Window
3
+ * Main window proc. Owns editor view lifecycle, keyboard, mouse, paint.
4
+ */
5
+
6
+ #include "window.h"
7
+ #include "application.h"
8
+ #include "../../core/events.h"
9
+ #include "../../editor/editor_view.h"
10
+ #include "../../editor/document.h"
11
+
12
+ #define WINDOW_CLASS_NAME L"SovereignIDE"
13
+ #define WINDOW_TITLE L"Sovereign IDE"
14
+
15
+ static EditorView *g_editor_view = NULL;
16
+ static Document g_doc;
17
+
18
+ static LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
19
+ switch (msg) {
20
+
21
+ case WM_CREATE: {
22
+ document_new(&g_doc);
23
+ buffer_insert(g_doc.buffer, 0,
24
+ "// Sovereign IDE\n// Type here to start editing.\n\nint main(void) {\n return 0;\n}\n",
25
+ 81);
26
+ buffer_mark_clean(g_doc.buffer);
27
+
28
+ if (editor_view_create(&g_editor_view, hwnd) == SOV_OK) {
29
+ editor_view_set_document(g_editor_view, &g_doc);
30
+ }
31
+
32
+ /* 530ms timer for caret blink */
33
+ SetTimer(hwnd, 1, 530, NULL);
34
+ return 0;
35
+ }
36
+
37
+ case WM_TIMER:
38
+ InvalidateRect(hwnd, NULL, FALSE);
39
+ return 0;
40
+
41
+ case WM_PAINT: {
42
+ PAINTSTRUCT ps;
43
+ BeginPaint(hwnd, &ps);
44
+ if (g_editor_view) {
45
+ RECT rc;
46
+ GetClientRect(hwnd, &rc);
47
+ editor_view_paint(g_editor_view, rc);
48
+ }
49
+ EndPaint(hwnd, &ps);
50
+ return 0;
51
+ }
52
+
53
+ case WM_SIZE: {
54
+ uint32_t w = LOWORD(lp);
55
+ uint32_t h = HIWORD(lp);
56
+ if (g_editor_view) editor_view_resize(g_editor_view, w, h);
57
+ Event ev = { .kind = EVENT_RESIZE };
58
+ ev.resize.width = w; ev.resize.height = h;
59
+ event_post(ev);
60
+ return 0;
61
+ }
62
+
63
+ case WM_KEYDOWN:
64
+ case WM_SYSKEYDOWN: {
65
+ if (g_editor_view) {
66
+ UINT mods = 0;
67
+ if (GetKeyState(VK_SHIFT) & 0x8000) mods |= 1;
68
+ if (GetKeyState(VK_CONTROL) & 0x8000) mods |= 2;
69
+ if (GetKeyState(VK_MENU) & 0x8000) mods |= 4;
70
+ editor_view_key_down(g_editor_view, (UINT)wp, mods);
71
+ }
72
+ return 0;
73
+ }
74
+
75
+ case WM_CHAR: {
76
+ if (g_editor_view) {
77
+ editor_view_char(g_editor_view, (wchar_t)wp);
78
+ }
79
+ return 0;
80
+ }
81
+
82
+ case WM_LBUTTONDOWN: {
83
+ SetCapture(hwnd);
84
+ if (g_editor_view) {
85
+ editor_view_mouse_down(g_editor_view,
86
+ (int)(short)LOWORD(lp), (int)(short)HIWORD(lp));
87
+ }
88
+ return 0;
89
+ }
90
+
91
+ case WM_LBUTTONUP:
92
+ ReleaseCapture();
93
+ return 0;
94
+
95
+ case WM_MOUSEMOVE: {
96
+ if (g_editor_view) {
97
+ bool btn = (wp & MK_LBUTTON) != 0;
98
+ editor_view_mouse_move(g_editor_view,
99
+ (int)(short)LOWORD(lp), (int)(short)HIWORD(lp), btn);
100
+ }
101
+ return 0;
102
+ }
103
+
104
+ case WM_MOUSEWHEEL: {
105
+ if (g_editor_view) {
106
+ int delta = GET_WHEEL_DELTA_WPARAM(wp);
107
+ /* Forward as key events for scroll */
108
+ UINT vk = (delta > 0) ? VK_UP : VK_DOWN;
109
+ for (int i = 0; i < SCROLL_LINES; i++) {
110
+ event_post((Event){ .kind = EVENT_MOUSE_WHEEL });
111
+ }
112
+ (void)vk;
113
+ }
114
+ return 0;
115
+ }
116
+
117
+ case WM_SETFOCUS:
118
+ event_post((Event){ .kind = EVENT_FOCUS_GAINED });
119
+ return 0;
120
+
121
+ case WM_KILLFOCUS:
122
+ event_post((Event){ .kind = EVENT_FOCUS_LOST });
123
+ return 0;
124
+
125
+ case WM_DPICHANGED: {
126
+ RECT *r = (RECT *)lp;
127
+ SetWindowPos(hwnd, NULL, r->left, r->top,
128
+ r->right - r->left, r->bottom - r->top,
129
+ SWP_NOZORDER | SWP_NOACTIVATE);
130
+ return 0;
131
+ }
132
+
133
+ case WM_CLOSE:
134
+ KillTimer(hwnd, 1);
135
+ if (g_editor_view) { editor_view_destroy(g_editor_view); g_editor_view = NULL; }
136
+ document_destroy(&g_doc);
137
+ event_post((Event){ .kind = EVENT_QUIT });
138
+ app_request_quit();
139
+ return 0;
140
+
141
+ case WM_DESTROY:
142
+ PostQuitMessage(0);
143
+ return 0;
144
+ }
145
+
146
+ return DefWindowProcW(hwnd, msg, wp, lp);
147
+ }
148
+
149
+ HWND window_create(int width, int height) {
150
+ WNDCLASSEXW wc = {0};
151
+ wc.cbSize = sizeof(wc);
152
+ wc.style = CS_HREDRAW | CS_VREDRAW;
153
+ wc.lpfnWndProc = window_proc;
154
+ wc.hInstance = app_get_hinstance();
155
+ wc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
156
+ wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
157
+ wc.lpszClassName = WINDOW_CLASS_NAME;
158
+ RegisterClassExW(&wc);
159
+
160
+ HWND hwnd = CreateWindowExW(
161
+ 0,
162
+ WINDOW_CLASS_NAME,
163
+ WINDOW_TITLE,
164
+ WS_OVERLAPPEDWINDOW,
165
+ CW_USEDEFAULT, CW_USEDEFAULT,
166
+ width, height,
167
+ NULL, NULL,
168
+ app_get_hinstance(),
169
+ NULL
170
+ );
171
+
172
+ ShowWindow(hwnd, SW_SHOW);
173
+ UpdateWindow(hwnd);
174
+ return hwnd;
175
+ }
ide/native/platform/windows/window.h ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef SOVEREIGN_WINDOW_H
2
+ #define SOVEREIGN_WINDOW_H
3
+
4
+ #include <windows.h>
5
+
6
+ #define SCROLL_LINES 3
7
+
8
+ HWND window_create(int width, int height);
9
+
10
+ #endif
ide/native/terminal/conpty.c ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Sovereign IDE — ConPTY Terminal
3
+ * Windows Pseudo Console for embedded terminal.
4
+ * ConPTY APIs are loaded dynamically since MinGW headers may lack them.
5
+ */
6
+
7
+ #include "conpty.h"
8
+ #include "../core/arena.h"
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+
12
+ /* ConPTY types and dynamic loading (Win10 1809+) */
13
+ typedef void *HPCON;
14
+
15
+ typedef HRESULT (WINAPI *PFN_CreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
16
+ typedef HRESULT (WINAPI *PFN_ResizePseudoConsole)(HPCON, COORD);
17
+ typedef void (WINAPI *PFN_ClosePseudoConsole)(HPCON);
18
+
19
+ #ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
20
+ #define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
21
+ #endif
22
+
23
+ static PFN_CreatePseudoConsole pfnCreatePseudoConsole;
24
+ static PFN_ResizePseudoConsole pfnResizePseudoConsole;
25
+ static PFN_ClosePseudoConsole pfnClosePseudoConsole;
26
+ static bool conpty_loaded = false;
27
+
28
+ static bool conpty_load(void) {
29
+ if (conpty_loaded) return pfnCreatePseudoConsole != NULL;
30
+ conpty_loaded = true;
31
+ HMODULE k32 = GetModuleHandleW(L"kernel32.dll");
32
+ if (!k32) return false;
33
+ pfnCreatePseudoConsole = (PFN_CreatePseudoConsole)(void *)GetProcAddress(k32, "CreatePseudoConsole");
34
+ pfnResizePseudoConsole = (PFN_ResizePseudoConsole)(void *)GetProcAddress(k32, "ResizePseudoConsole");
35
+ pfnClosePseudoConsole = (PFN_ClosePseudoConsole)(void *)GetProcAddress(k32, "ClosePseudoConsole");
36
+ return pfnCreatePseudoConsole != NULL;
37
+ }
38
+
39
+ struct Terminal {
40
+ HPCON hpc;
41
+ HANDLE pipe_in;
42
+ HANDLE pipe_out;
43
+ HANDLE pipe_pty_in;
44
+ HANDLE pipe_pty_out;
45
+ PROCESS_INFORMATION pi;
46
+ bool alive;
47
+ };
48
+
49
+ SovResult terminal_create(Terminal **out, uint16_t cols, uint16_t rows) {
50
+ if (!conpty_load()) return SOV_ERR_ALLOC;
51
+
52
+ Terminal *t = (Terminal *)calloc(1, sizeof(Terminal));
53
+ if (!t) return SOV_ERR_ALLOC;
54
+
55
+ HANDLE pipe_pty_in, pipe_in;
56
+ HANDLE pipe_pty_out, pipe_out;
57
+ CreatePipe(&pipe_pty_in, &pipe_in, NULL, 0);
58
+ CreatePipe(&pipe_out, &pipe_pty_out, NULL, 0);
59
+
60
+ COORD size = { .X = (SHORT)cols, .Y = (SHORT)rows };
61
+ HRESULT hr = pfnCreatePseudoConsole(size, pipe_pty_in, pipe_pty_out, 0, &t->hpc);
62
+ if (FAILED(hr)) {
63
+ CloseHandle(pipe_pty_in);
64
+ CloseHandle(pipe_in);
65
+ CloseHandle(pipe_pty_out);
66
+ CloseHandle(pipe_out);
67
+ free(t);
68
+ return SOV_ERR_ALLOC;
69
+ }
70
+
71
+ t->pipe_in = pipe_in;
72
+ t->pipe_out = pipe_out;
73
+ t->pipe_pty_in = pipe_pty_in;
74
+ t->pipe_pty_out = pipe_pty_out;
75
+
76
+ STARTUPINFOEXW si;
77
+ memset(&si, 0, sizeof(si));
78
+ si.StartupInfo.cb = sizeof(si);
79
+
80
+ SIZE_T attr_size = 0;
81
+ InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size);
82
+ si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(attr_size);
83
+ InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attr_size);
84
+ UpdateProcThreadAttribute(si.lpAttributeList, 0,
85
+ PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, t->hpc, sizeof(HPCON), NULL, NULL);
86
+
87
+ wchar_t cmd[] = L"cmd.exe";
88
+ CreateProcessW(NULL, cmd, NULL, NULL, FALSE,
89
+ EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
90
+ &si.StartupInfo, &t->pi);
91
+
92
+ DeleteProcThreadAttributeList(si.lpAttributeList);
93
+ free(si.lpAttributeList);
94
+
95
+ CloseHandle(pipe_pty_in);
96
+ CloseHandle(pipe_pty_out);
97
+
98
+ t->alive = true;
99
+ *out = t;
100
+ return SOV_OK;
101
+ }
102
+
103
+ SovResult terminal_write(Terminal *t, const char *data, size_t len) {
104
+ DWORD written;
105
+ if (!WriteFile(t->pipe_in, data, (DWORD)len, &written, NULL))
106
+ return SOV_ERR_IO;
107
+ return SOV_OK;
108
+ }
109
+
110
+ SovResult terminal_read(Terminal *t, char *buf, size_t max_len, size_t *out_len) {
111
+ DWORD available = 0;
112
+ if (!PeekNamedPipe(t->pipe_out, NULL, 0, NULL, &available, NULL) || available == 0) {
113
+ *out_len = 0;
114
+ return SOV_OK;
115
+ }
116
+
117
+ DWORD to_read = (available < (DWORD)max_len) ? available : (DWORD)max_len;
118
+ DWORD nread = 0;
119
+ if (!ReadFile(t->pipe_out, buf, to_read, &nread, NULL))
120
+ return SOV_ERR_IO;
121
+
122
+ *out_len = nread;
123
+ return SOV_OK;
124
+ }
125
+
126
+ void terminal_resize(Terminal *t, uint16_t cols, uint16_t rows) {
127
+ if (pfnResizePseudoConsole) {
128
+ COORD size = { .X = (SHORT)cols, .Y = (SHORT)rows };
129
+ pfnResizePseudoConsole(t->hpc, size);
130
+ }
131
+ }
132
+
133
+ bool terminal_is_alive(const Terminal *t) {
134
+ if (!t->alive) return false;
135
+ DWORD exit_code;
136
+ GetExitCodeProcess(t->pi.hProcess, &exit_code);
137
+ return exit_code == STILL_ACTIVE;
138
+ }
139
+
140
+ void terminal_destroy(Terminal *t) {
141
+ if (!t) return;
142
+ if (pfnClosePseudoConsole) pfnClosePseudoConsole(t->hpc);
143
+ CloseHandle(t->pipe_in);
144
+ CloseHandle(t->pipe_out);
145
+ TerminateProcess(t->pi.hProcess, 0);
146
+ CloseHandle(t->pi.hProcess);
147
+ CloseHandle(t->pi.hThread);
148
+ free(t);
149
+ }