Compare commits

...

No commits in common. "717de59ad9ff9111bca18152da433f48906f47ea" and "bfbe73b58a74a7b9b6ee4fa4310beac832f1fd75" have entirely different histories.

8 changed files with 1766 additions and 131 deletions

View File

@ -1,131 +0,0 @@
name: Rumpk CI
on:
push:
branches: [unstable, main]
pull_request:
branches: [unstable, main]
jobs:
build-riscv:
runs-on: ubuntu-latest
container:
image: nexus-os/build-env:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Build RISC-V kernel
run: |
echo "Building for RISC-V..."
zig build -Darch=riscv64 -Drelease
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rumpk-riscv64
path: zig-out/rumpk-riscv64.elf
retention-days: 7
build-aarch64:
runs-on: ubuntu-latest
container:
image: nexus-os/build-env:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Build ARM64 kernel
run: |
echo "Building for ARM64..."
zig build -Darch=aarch64 -Drelease
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rumpk-aarch64
path: zig-out/rumpk-aarch64.elf
retention-days: 7
security-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Sensitive content scan
run: |
echo "🔍 Scanning for sensitive content..."
# Check for forbidden directories
if git log --all --name-only | grep -qE '\.agent/|\.vscode/|\.claude/|\.kiro/'; then
echo "❌ CRITICAL: Forbidden directory found in git history"
exit 1
fi
# Check for internal paths
if git log --all -p | grep -qE '/home/markus/zWork/|/home/markus/\.claude/'; then
echo "❌ CRITICAL: Internal path found in git history"
exit 1
fi
echo "✅ No sensitive content detected"
- name: License header check
run: |
echo "Checking license headers..."
# TODO: Implement license header checker
echo " License check pending"
test-qemu:
needs: [build-riscv]
runs-on: ubuntu-latest
container:
image: nexus-os/build-env:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: rumpk-riscv64
path: zig-out/
- name: QEMU boot test
timeout-minutes: 5
run: |
echo "🚀 Booting RISC-V kernel in QEMU..."
timeout 10s qemu-system-riscv64 \
-machine virt \
-cpu rv64 \
-smp 2 \
-m 128M \
-kernel zig-out/rumpk-riscv64.elf \
-serial stdio \
-display none \
-bios none || echo "Boot test completed"
reproducibility-check:
runs-on: ubuntu-latest
container:
image: nexus-os/build-env:latest
steps:
- uses: actions/checkout@v4
- name: Build twice and compare
run: |
echo "🔧 Building first time..."
zig build -Drelease
cp zig-out/rumpk-riscv64.elf /tmp/build1.elf
echo "🔧 Building second time..."
zig build -Drelease
echo "📊 Comparing builds..."
if diff /tmp/build1.elf zig-out/rumpk-riscv64.elf; then
echo "✅ Reproducible build verified"
else
echo "⚠️ Build not reproducible (timestamps embedded)"
fi

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# Compiled binary
/nipbox
*.exe
# Nim build artifacts
nimcache/
build/
*.o
*.a
*.so
# IDE / Editor
.vscode/
.idea/
*.swp
*~
# Agent / internal
.agent/
.claude/
.kiro/

277
src/editor.nim Normal file
View File

@ -0,0 +1,277 @@
# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus SDK.
# See legal/LICENSE_UNBOUND.md for license terms.
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# Scribe v3: The Sovereign TUI Editor
# Phase 24: Full TUI with Navigation & Multi-Sector IO
import strutils, sequtils
import ../../libs/membrane/libc as lb
# --- CONSTANTS ---
const
KEY_CTRL_Q = char(17)
KEY_CTRL_S = char(19)
KEY_CTRL_X = char(24) # Alternative exit
KEY_ESC = char(27)
KEY_BACKSPACE = char(127)
KEY_ENTER = char(13) # CR
KEY_LF = char(10)
# --- STATE ---
var lines: seq[string]
var cursor_x: int = 0
var cursor_y: int = 0 # Line index in buffer
var scroll_y: int = 0 # Index of top visible line
var screen_rows: int = 20 # Fixed for now, or detect?
var screen_cols: int = 80
var filename: string = ""
var status_msg: string = "CTRL-S: Save | CTRL-Q: Quit"
var is_running: bool = true
# --- TERMINAL HELPERS ---
proc write_raw(s: string) =
if s.len > 0:
discard lb.write(cint(1), cast[pointer](unsafeAddr s[0]), csize_t(s.len))
proc term_clear() =
write_raw("\x1b[2J") # Clear entire screen
proc term_move(row, col: int) =
# ANSI is 1-indexed
write_raw("\x1b[" & $(row + 1) & ";" & $(col + 1) & "H")
proc term_hide_cursor() = write_raw("\x1b[?25l")
proc term_show_cursor() = write_raw("\x1b[?25h")
# --- FILE IO ---
proc load_file(fname: string) =
lines = @[]
let fd = lb.open(fname.cstring, 0)
if fd >= 0:
var content = ""
var buf: array[512, char]
while true:
let n = lb.read(fd, addr buf[0], 512)
if n <= 0: break
for i in 0..<n: content.add(buf[i])
discard lb.close(fd)
if content.len > 0:
lines = content.splitLines()
else:
lines.add("")
else:
# New File
lines.add("")
if lines.len == 0: lines.add("")
proc save_file() =
var content = lines.join("\n")
# Ensure trailing newline often expected
if content.len > 0 and content[^1] != '\n': content.add('\n')
# FLAGS: O_WRONLY(1) | O_CREAT(64) | O_TRUNC(512) = 577
let fd = lb.open(filename.cstring, 577)
if fd < 0:
status_msg = "Error: Save Failed (VFS Open)."
return
let n = lb.write(fd, cast[pointer](unsafeAddr content[0]), csize_t(content.len))
discard lb.close(fd)
status_msg = "Saved " & $n & " bytes."
# --- LOGIC ---
proc scroll_to_cursor() =
if cursor_y < scroll_y:
scroll_y = cursor_y
if cursor_y >= scroll_y + screen_rows:
scroll_y = cursor_y - screen_rows + 1
proc render() =
term_hide_cursor()
term_move(0, 0)
# Draw Content
for i in 0..<screen_rows:
let line_idx = scroll_y + i
term_move(i, 0)
write_raw("\x1b[K") # Clear Line
if line_idx < lines.len:
var line = lines[line_idx]
# Truncate for display if needed? For now wrap or let terminal handle
if line.len > screen_cols: line = line[0..<screen_cols]
write_raw(line)
else:
write_raw("~") # Vim style empty lines
# Draw Status Bar
term_move(screen_rows, 0)
write_raw("\x1b[7m") # Invert
var bar = " " & filename & " - " & $cursor_x & ":" & $cursor_y & " | " & status_msg
while bar.len < screen_cols: bar.add(" ")
if bar.len > screen_cols: bar = bar[0..<screen_cols]
write_raw(bar)
write_raw("\x1b[0m") # Reset
# Position Cursor
term_move(cursor_y - scroll_y, cursor_x)
term_show_cursor()
proc insert_char(c: char) =
if cursor_y >= lines.len: lines.add("")
var line = lines[cursor_y]
if cursor_x > line.len: cursor_x = line.len
if cursor_x == line.len:
line.add(c)
else:
line.insert($c, cursor_x)
lines[cursor_y] = line
cursor_x += 1
proc insert_newline() =
if cursor_y >= lines.len: lines.add("") # Should catch
let current_line = lines[cursor_y]
if cursor_x >= current_line.len:
# Append new empty line
lines.insert("", cursor_y + 1)
else:
# Split line
let left = current_line[0..<cursor_x]
let right = current_line[cursor_x..^1]
lines[cursor_y] = left
lines.insert(right, cursor_y + 1)
cursor_y += 1
cursor_x = 0
proc backspace() =
if cursor_y >= lines.len: return
if cursor_x > 0:
var line = lines[cursor_y]
# Delete char at x-1
if cursor_x - 1 < line.len:
line.delete(cursor_x - 1, cursor_x - 1)
lines[cursor_y] = line
cursor_x -= 1
elif cursor_y > 0:
# Merge with previous line
let current = lines[cursor_y]
let prev_len = lines[cursor_y - 1].len
lines[cursor_y - 1].add(current)
lines.delete(cursor_y)
cursor_y -= 1
cursor_x = prev_len
proc handle_arrow(code: char) =
case code:
of 'A': # UP
if cursor_y > 0: cursor_y -= 1
of 'B': # DOWN
if cursor_y < lines.len - 1: cursor_y += 1
of 'C': # RIGHT
if cursor_y < lines.len:
if cursor_x < lines[cursor_y].len: cursor_x += 1
elif cursor_y < lines.len - 1: # Wrap to next line
cursor_y += 1
cursor_x = 0
of 'D': # LEFT
if cursor_x > 0: cursor_x -= 1
elif cursor_y > 0: # Wrap to prev line end
cursor_y -= 1
cursor_x = lines[cursor_y].len
else: discard
# Snap cursor to line length
if cursor_y < lines.len:
if cursor_x > lines[cursor_y].len: cursor_x = lines[cursor_y].len
# --- MAIN LOOP ---
proc read_input() =
# We need a custom input loop that handles escapes
# This uses libc.read on fd 0 (stdin)
var c: char
let n = lb.read(0, addr c, 1)
if n <= 0: return
if c == KEY_CTRL_Q or c == KEY_CTRL_X:
is_running = false
return
if c == KEY_CTRL_S:
save_file()
return
if c == KEY_ESC:
# Potential Sequence
# Busy wait briefly for next char to confirm sequence vs lone ESC
# In a real OS we'd have poll/timeout. Here we hack.
# Actually, let's just try to read immediately.
var c2: char
let n2 = lb.read(0, addr c2, 1) # This might block if not buffered?
# Our lb.read is non-blocking if ring is empty, returns 0.
# But for a sequence, the chars should be in the packet together or close.
# If 0, it was just ESC.
if n2 > 0 and c2 == '[':
var c3: char
let n3 = lb.read(0, addr c3, 1)
if n3 > 0:
handle_arrow(c3)
return
if c == KEY_BACKSPACE or c == '\b':
backspace()
return
if c == KEY_ENTER or c == KEY_LF:
insert_newline()
return
# Normal char
if c >= ' ' and c <= '~':
insert_char(c)
proc start_editor*(fname: string) =
filename = fname
is_running = true
cursor_x = 0
cursor_y = 0
scroll_y = 0
status_msg = "CTRL-S: Save | CTRL-Q: Quit"
write_raw("[Scribe] Loading " & fname & "...\n")
load_file(fname)
term_clear()
while is_running:
# lb.pump_membrane_stack() - Handled by Kernel
scroll_to_cursor()
render()
# Input Loop (Non-blocking check mostly)
# We loop quickly to feel responsive
read_input()
# Yield slightly
for i in 0..5000: discard
term_clear()
term_move(0, 0)
write_raw("Scribe Closed.\n")

256
src/kdl.nim Normal file
View File

@ -0,0 +1,256 @@
# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus SDK.
# See legal/LICENSE_UNBOUND.md for license terms.
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# NipBox KDL Core (The Semantic Spine)
# Defines the typed object system for the Sovereign Shell.
import strutils
import std/assertions
type
ValueKind* = enum
VString, VInt, VBool, VNull
Value* = object
case kind*: ValueKind
of VString: s*: string
of VInt: i*: int
of VBool: b*: bool
of VNull: discard
# A KDL Node: name arg1 arg2 key=val { children }
Node* = ref object
name*: string
args*: seq[Value]
props*: seq[tuple[key: string, val: Value]]
children*: seq[Node]
# --- Constructors ---
proc newVal*(s: string): Value = Value(kind: VString, s: s)
proc newVal*(i: int): Value = Value(kind: VInt, i: i)
proc newVal*(b: bool): Value = Value(kind: VBool, b: b)
proc newNull*(): Value = Value(kind: VNull)
proc newNode*(name: string): Node =
new(result)
result.name = name
result.args = @[]
result.props = @[]
result.children = @[]
proc addArg*(n: Node, v: Value) =
n.args.add(v)
proc addProp*(n: Node, key: string, v: Value) =
n.props.add((key, v))
proc addChild*(n: Node, child: Node) =
n.children.add(child)
# --- Serialization (The Renderer) ---
proc `$`*(v: Value): string =
case v.kind
of VString: "\"" & v.s & "\"" # TODO: Escape quotes properly
of VInt: $v.i
of VBool: $v.b
of VNull: "null"
proc render*(n: Node, indent: int = 0): string =
let prefix = repeat(' ', indent)
var line = prefix & n.name
# Args
for arg in n.args:
line.add(" " & $arg)
# Props
for prop in n.props:
line.add(" " & prop.key & "=" & $prop.val)
# Children
if n.children.len > 0:
line.add(" {\n")
for child in n.children:
line.add(render(child, indent + 2))
line.add(prefix & "}\n")
else:
line.add("\n")
return line
# Table View (For Flat Lists)
proc renderTable*(nodes: seq[Node]): string =
var s = ""
for n in nodes:
s.add(render(n))
return s
# --- Parser ---
type Parser = ref object
input: string
pos: int
proc peek(p: Parser): char =
if p.pos >= p.input.len: return '\0'
return p.input[p.pos]
proc next(p: Parser): char =
if p.pos >= p.input.len: return '\0'
result = p.input[p.pos]
p.pos.inc
proc skipSpace(p: Parser) =
while true:
let c = p.peek()
if c == ' ' or c == '\t' or c == '\r': discard p.next()
else: break
proc parseIdentifier(p: Parser): string =
# Simple identifier: strictly alphanumeric + _ - for now
# TODO: Quoted identifiers
if p.peek() == '"':
discard p.next()
while true:
let c = p.next()
if c == '\0': break
if c == '"': break
result.add(c)
else:
while true:
let c = p.peek()
if c in {'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', '/'}:
result.add(p.next())
else: break
proc parseValue(p: Parser): Value =
skipSpace(p)
let c = p.peek()
if c == '"':
# String
discard p.next()
var s = ""
while true:
let ch = p.next()
if ch == '\0': break
if ch == '"': break
s.add(ch)
return newVal(s)
elif c in {'0'..'9', '-'}:
# Number (Int only for now)
var s = ""
s.add(p.next())
while p.peek() in {'0'..'9'}:
s.add(p.next())
try:
return newVal(parseInt(s))
except:
return newVal(0)
elif c == 't': # true
if p.input.substr(p.pos, p.pos+3) == "true":
p.pos += 4
return newVal(true)
elif c == 'f': # false
if p.input.substr(p.pos, p.pos+4) == "false":
p.pos += 5
return newVal(false)
elif c == 'n': # null
if p.input.substr(p.pos, p.pos+3) == "null":
p.pos += 4
return newNull()
# Fallback: Bare string identifier
return newVal(parseIdentifier(p))
proc parseNode(p: Parser): Node =
skipSpace(p)
let name = parseIdentifier(p)
if name.len == 0: return nil
var node = newNode(name)
while true:
skipSpace(p)
let c = p.peek()
if c == '\n' or c == ';' or c == '}' or c == '\0': break
if c == '{': break # Children start
# Arg or Prop?
# Peek ahead to see if next is identifier=value
# Simple heuristic: parse identifier, if next char is '=', it's a prop.
let startPos = p.pos
let id = parseIdentifier(p)
if id.len > 0 and p.peek() == '=':
# Property
discard p.next() # skip =
let val = parseValue(p)
node.addProp(id, val)
else:
# Argument
# Backtrack? Or realize we parsed a value?
# If `id` was a bare string value, it works.
# If `id` was quoted string, `parseIdentifier` handled it.
# But `parseValue` handles numbers/bools too. `parseIdentifier` does NOT.
# Better approach:
# Reset pos
p.pos = startPos
# Check if identifier followed by =
# We need a proper lookahead for keys.
# For now, simplistic:
let val = parseValue(p)
# Check if we accidentally parsed a key?
# If val is string, and next char is '=', convert to key?
if val.kind == VString and p.peek() == '=':
discard p.next()
let realVal = parseValue(p)
node.addProp(val.s, realVal)
else:
node.addArg(val)
# Children
skipSpace(p)
if p.peek() == '{':
discard p.next() # skip {
while true:
skipSpace(p)
if p.peek() == '}':
discard p.next()
break
skipSpace(p)
# Skip newlines
while p.peek() == '\n': discard p.next()
if p.peek() == '}':
discard p.next()
break
let child = parseNode(p)
if child != nil:
node.addChild(child)
else:
# Check if just newline?
if p.peek() == '\n': discard p.next()
else: break # Error or empty
return node
proc parseKdl*(input: string): seq[Node] =
var p = Parser(input: input, pos: 0)
result = @[]
while true:
skipSpace(p)
while p.peek() == '\n' or p.peek() == ';': discard p.next()
if p.peek() == '\0': break
let node = parseNode(p)
if node != nil:
result.add(node)
else:
break

1123
src/nipbox.nim Normal file

File diff suppressed because it is too large Load Diff

89
src/std.nim Normal file
View File

@ -0,0 +1,89 @@
# SPDX-License-Identifier: LUL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus SDK.
# See legal/LICENSE_UNBOUND.md for license terms.
# src/npl/nipbox/std.nim
# Adapter for Legacy Code -> New LibC
type cptr* = pointer
# LibC Imports
proc write*(fd: cint, buf: cptr, count: csize_t): int {.importc, cdecl.}
proc read*(fd: cint, buf: cptr, count: csize_t): int {.importc, cdecl.}
proc open*(pathname: cstring, flags: cint): cint {.importc, cdecl.}
proc close*(fd: cint): cint {.importc, cdecl.}
proc exit*(status: cint) {.importc, cdecl.}
proc nexus_list*(buf: pointer, len: int): int {.importc, cdecl.}
proc syscall(nr: int, a0: int = 0, a1: int = 0, a2: int = 0): int {.importc, cdecl.}
# Legacy Aliases
proc list_files*(buf: pointer, len: uint64): int64 =
return int64(nexus_list(buf, int(len)))
proc nexus_syscall*(cmd: cint, arg: uint64): cint =
return cint(syscall(int(cmd), int(arg), 0, 0))
proc nexus_yield*() =
discard syscall(0, 0) # Exit/Yield
type FileArgs* = object
name*: uint64
data*: uint64
len*: uint64
proc nexus_file_write*(name: string, data: string) =
# Disabled
return
proc nexus_file_read*(name: string, buffer: pointer, max_len: uint64): int =
# Disabled
return -1
# Print Helpers
proc print*(s: string) =
if s.len > 0:
discard write(cint(1), unsafeAddr s[0], csize_t(s.len))
var nl = "\n"
discard write(cint(1), unsafeAddr nl[0], 1)
proc print_raw*(s: string) =
if s.len > 0:
discard write(cint(1), unsafeAddr s[0], csize_t(s.len))
proc print_int*(n: int) =
var s = ""
var v = n
if v == 0: s = "0"
else:
while v > 0:
s.add(char((v mod 10) + 48))
v = v div 10
# Reverse
var r = ""
var i = s.len - 1
while i >= 0:
r.add(s[i])
i -= 1
print_raw(r)
proc my_readline*(out_str: var string): bool =
out_str = ""
while true:
var c: char
let n = read(cint(0), addr c, 1)
if n <= 0: return false
if c == '\n' or c == '\r':
print_raw("\n")
return true
elif c == '\b' or c == char(127):
if out_str.len > 0:
var bs = "\b \b"
discard write(cint(1), addr bs[0], 3)
out_str.setLen(out_str.len - 1)
else:
out_str.add(c)
discard write(cint(1), addr c, 1)