feat: port arbor, dharma, forge El source into monorepo
Brings the remaining foundation repos that were not included in the original monorepo consolidation: - arbor/vessels/ — 6 vessels (arbor-cli, arbor-core, arbor-diagram, arbor-layout, arbor-parse, arbor-render) with manifests + src/main.el - dharma/ — CGI Provenance Registry package (flat layout, 14 .el files across registry/, sandbox/, training/, validation/, tests/) - forge/ — consciousness channel tool (8 src .el files + new manifest.el) - elp/src/ — 36 test fixture files not carried over in original merge (dedup_*, realizer_*, semantics_*, morph_*, ext_*, one_extern_* helpers) el-ide, engram, elql are already complete in ide/, engram/, ql/.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
// arbor-core — fundamental types for Arbor diagrams.
|
||||
// Node IDs (sanitised), shape vocabulary, edge kinds, and the lightweight
|
||||
// graph value used by every other vessel.
|
||||
|
||||
vessel "arbor-core" {
|
||||
version "0.1.0"
|
||||
description "Core types for Arbor diagrams: NodeId, ArborShape, ArborEdgeKind, graphs"
|
||||
authors ["Neuron Technologies"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// arbor-core — core types for Arbor diagrams.
|
||||
//
|
||||
// Idiomatic El: everything is a Map. Functions take/return maps; helpers are
|
||||
// pure and small. The downstream vessels (parse, layout, render) consume the
|
||||
// shapes defined here.
|
||||
//
|
||||
// Shape vocabulary:
|
||||
// ArborShape strings — "rect" "rounded" "cylinder" "diamond" "stadium" "primary"
|
||||
//
|
||||
// Edge-kind strings:
|
||||
// "solid" "dashed" "forbidden" "bidirectional"
|
||||
//
|
||||
// Node value: { "id":Str, "label":Str, "shape":Str }
|
||||
// Edge value: { "from":Str, "to":Str, "label":Str, "kind":Str }
|
||||
// Group value: { "id":Str, "label":Str, "node_ids":[Str], "direction":Str }
|
||||
// Graph value: { "title":Str, "direction":Str, "nodes":[Node], "edges":[Edge], "groups":[Group] }
|
||||
//
|
||||
// Diagram-form (lowered) is the same shape but with NodeStyle/EdgeLine/Arrow
|
||||
// resolved into renderer-friendly fields:
|
||||
// Node: + "sublabel":Str, "style_fill":Str, "style_stroke":Str, "style_color":Str
|
||||
// Edge: + "line":Str ("solid"/"dashed"/"dotted"/"thick"), "arrow":Str ("forward"/"backward"/"both"/"none")
|
||||
//
|
||||
// This file is the canonical definition of those shapes. Other vessels rely on
|
||||
// these field names.
|
||||
|
||||
// ── NodeId sanitisation ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Sanitise an arbitrary string into a Mermaid-safe identifier.
|
||||
// - any char not in [a-zA-Z0-9_] becomes '_'
|
||||
// - consecutive underscores collapse
|
||||
// - trailing underscores stripped
|
||||
// - if first char is a digit, prepend 'n'
|
||||
// - if empty, return "node"
|
||||
|
||||
fn is_alnum_underscore(ch: String) -> Bool {
|
||||
let code: Int = str_char_code(ch, 0)
|
||||
if code >= 48 {
|
||||
if code <= 57 { return true }
|
||||
}
|
||||
if code >= 65 {
|
||||
if code <= 90 { return true }
|
||||
}
|
||||
if code >= 97 {
|
||||
if code <= 122 { return true }
|
||||
}
|
||||
if code == 95 { return true }
|
||||
false
|
||||
}
|
||||
|
||||
fn is_ascii_digit(ch: String) -> Bool {
|
||||
let code: Int = str_char_code(ch, 0)
|
||||
if code >= 48 {
|
||||
if code <= 57 { return true }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn sanitize_id(s: String) -> String {
|
||||
let n: Int = str_len(s)
|
||||
if n == 0 { return "node" }
|
||||
|
||||
// Pass 1: replace and collapse.
|
||||
let out = ""
|
||||
let prev_underscore = false
|
||||
let i = 0
|
||||
while i < n {
|
||||
let ch: String = str_char_at(s, i)
|
||||
if is_alnum_underscore(ch) {
|
||||
let out = out + ch
|
||||
let prev_underscore = false
|
||||
} else {
|
||||
if !prev_underscore {
|
||||
let out = out + "_"
|
||||
}
|
||||
let prev_underscore = true
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Pass 2: strip trailing underscores.
|
||||
let m: Int = str_len(out)
|
||||
let end = m
|
||||
let stripping = true
|
||||
while stripping {
|
||||
if end <= 0 {
|
||||
let stripping = false
|
||||
} else {
|
||||
let last: String = str_char_at(out, end - 1)
|
||||
if last == "_" {
|
||||
let end = end - 1
|
||||
} else {
|
||||
let stripping = false
|
||||
}
|
||||
}
|
||||
}
|
||||
let out = str_slice(out, 0, end)
|
||||
|
||||
if str_len(out) == 0 { return "node" }
|
||||
|
||||
// Pass 3: leading-digit guard.
|
||||
let first: String = str_char_at(out, 0)
|
||||
if is_ascii_digit(first) {
|
||||
let out = "n" + out
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// ── Constructors ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_node(id: String, label: String, shape: String) -> Map<String, Any> {
|
||||
{ "id": id, "label": label, "shape": shape }
|
||||
}
|
||||
|
||||
fn make_edge(src: String, dst: String, kind: String) -> Map<String, Any> {
|
||||
{ "from": src, "to": dst, "label": "", "kind": kind }
|
||||
}
|
||||
|
||||
fn make_edge_with_label(src: String, dst: String, kind: String, label: String) -> Map<String, Any> {
|
||||
{ "from": src, "to": dst, "label": label, "kind": kind }
|
||||
}
|
||||
|
||||
fn make_group(id: String, label: String) -> Map<String, Any> {
|
||||
let empty_ids: [String] = el_list_empty()
|
||||
{ "id": id, "label": label, "node_ids": empty_ids, "direction": "" }
|
||||
}
|
||||
|
||||
fn make_graph() -> Map<String, Any> {
|
||||
let empty_n: [Map<String, Any>] = el_list_empty()
|
||||
let empty_e: [Map<String, Any>] = el_list_empty()
|
||||
let empty_g: [Map<String, Any>] = el_list_empty()
|
||||
{ "title": "", "direction": "top-down",
|
||||
"nodes": empty_n, "edges": empty_e, "groups": empty_g }
|
||||
}
|
||||
|
||||
// ── Shape vocabulary ──────────────────────────────────────────────────────────
|
||||
// Returns the canonical shape string for a token, or "" if unknown.
|
||||
|
||||
fn shape_from_token(tok: String) -> String {
|
||||
let t: String = str_trim(tok)
|
||||
if t == "rect" { return "rect" }
|
||||
if t == "rounded" { return "rounded" }
|
||||
if t == "cylinder" { return "cylinder" }
|
||||
if t == "diamond" { return "diamond" }
|
||||
if t == "stadium" { return "stadium" }
|
||||
if t == "primary" { return "primary" }
|
||||
""
|
||||
}
|
||||
|
||||
// Lower an Arbor shape into the renderer's NodeShape vocabulary.
|
||||
fn shape_to_node_shape(shape: String) -> String {
|
||||
if shape == "rect" { return "rectangle" }
|
||||
if shape == "primary" { return "rectangle" }
|
||||
if shape == "rounded" { return "rounded_rect" }
|
||||
if shape == "cylinder" { return "cylinder" }
|
||||
if shape == "diamond" { return "diamond" }
|
||||
if shape == "stadium" { return "stadium" }
|
||||
"rectangle"
|
||||
}
|
||||
|
||||
// ── Lowering: ArborGraph → DiagramGraph ──────────────────────────────────────
|
||||
//
|
||||
// Replaces every node with a diagram-form node carrying explicit style fields,
|
||||
// and every edge with a diagram-form edge carrying line/arrow strings.
|
||||
|
||||
fn lower_node(n: Map<String, Any>) -> Map<String, Any> {
|
||||
let shape: String = n["shape"]
|
||||
let node_shape: String = shape_to_node_shape(shape)
|
||||
let fill = ""
|
||||
let stroke = ""
|
||||
let color = ""
|
||||
if shape == "primary" {
|
||||
let fill = "#0052A0"
|
||||
let stroke = "#0052A0"
|
||||
let color = "#ffffff"
|
||||
}
|
||||
{ "id": n["id"], "label": n["label"], "sublabel": "",
|
||||
"shape": node_shape,
|
||||
"style_fill": fill, "style_stroke": stroke, "style_color": color }
|
||||
}
|
||||
|
||||
fn lower_edge(e: Map<String, Any>) -> Map<String, Any> {
|
||||
let kind: String = e["kind"]
|
||||
let line = "solid"
|
||||
let arrow = "forward"
|
||||
if kind == "dashed" {
|
||||
let line = "dashed"
|
||||
}
|
||||
if kind == "bidirectional" {
|
||||
let arrow = "both"
|
||||
}
|
||||
// forbidden uses solid line + forward arrow; the renderer overlays the
|
||||
// circle-X marker based on a forbidden-set the caller threads through.
|
||||
{ "from": e["from"], "to": e["to"], "label": e["label"],
|
||||
"line": line, "arrow": arrow }
|
||||
}
|
||||
|
||||
fn lower_graph(g: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = g["nodes"]
|
||||
let edges: [Map<String, Any>] = g["edges"]
|
||||
let lowered_nodes: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
let n: Int = el_list_len(nodes)
|
||||
while i < n {
|
||||
let lowered_nodes = native_list_append(lowered_nodes, lower_node(get(nodes, i)))
|
||||
let i = i + 1
|
||||
}
|
||||
let lowered_edges: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
let m: Int = el_list_len(edges)
|
||||
while i < m {
|
||||
let lowered_edges = native_list_append(lowered_edges, lower_edge(get(edges, i)))
|
||||
let i = i + 1
|
||||
}
|
||||
{ "title": g["title"], "direction": g["direction"],
|
||||
"nodes": lowered_nodes, "edges": lowered_edges, "groups": g["groups"] }
|
||||
}
|
||||
|
||||
// Find a node by id within a (lowered or raw) graph. Returns an empty map
|
||||
// when not found — callers check map_get(result, "id") for presence.
|
||||
fn graph_find_node(graph: Map<String, Any>, id: String) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let node: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = node["id"]
|
||||
if nid == id { return node }
|
||||
let i = i + 1
|
||||
}
|
||||
let empty: Map<String, Any> = el_map_new(0)
|
||||
empty
|
||||
}
|
||||
|
||||
// ── Forbidden-edge set helpers ────────────────────────────────────────────────
|
||||
// The lowered graph drops the "forbidden" kind (line/arrow have no slot for
|
||||
// it). Callers preserve the set as a list of "from->to" strings.
|
||||
|
||||
fn forbidden_key(from: String, to: String) -> String {
|
||||
from + "->" + to
|
||||
}
|
||||
|
||||
fn collect_forbidden(graph: Map<String, Any>) -> [String] {
|
||||
let edges: [Map<String, Any>] = graph["edges"]
|
||||
let n: Int = el_list_len(edges)
|
||||
let out: [String] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n {
|
||||
let e: Map<String, Any> = get(edges, i)
|
||||
let kind: String = e["kind"]
|
||||
if kind == "forbidden" {
|
||||
let f: String = e["from"]
|
||||
let t: String = e["to"]
|
||||
let out = native_list_append(out, forbidden_key(f, t))
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn forbidden_contains(set: [String], src: String, dst: String) -> Bool {
|
||||
let key: String = forbidden_key(src, dst)
|
||||
let n: Int = el_list_len(set)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let s: String = get(set, i)
|
||||
if s == key { return true }
|
||||
let i = i + 1
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// ── Smoke test ────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// State is kept in process-local k/v storage so we never mix Int + Call or
|
||||
// Int + Ident in `+` (which the codegen heuristic emits as string concat
|
||||
// on tagged-pointer values, segfaulting on Int operands).
|
||||
|
||||
fn fail(label: String, got: String, want: String) -> Int {
|
||||
println("FAIL " + label + " got=[" + got + "] want=[" + want + "]")
|
||||
state_set("failures", "1")
|
||||
0
|
||||
}
|
||||
|
||||
fn check_eq(label: String, got: String, want: String) -> Int {
|
||||
if got == want {
|
||||
println("ok " + label + " = " + got)
|
||||
return 1
|
||||
}
|
||||
fail(label, got, want)
|
||||
}
|
||||
|
||||
check_eq("sanitize crates/nc-core",
|
||||
sanitize_id("crates/nc-core"), "crates_nc_core")
|
||||
|
||||
check_eq("sanitize package.json",
|
||||
sanitize_id("package.json"), "package_json")
|
||||
|
||||
check_eq("sanitize 42-module",
|
||||
sanitize_id("42-module"), "n42_module")
|
||||
|
||||
check_eq("sanitize empty", sanitize_id(""), "node")
|
||||
|
||||
check_eq("sanitize !!--@@", sanitize_id("!!--@@"), "node")
|
||||
|
||||
check_eq("shape_from_token rounded",
|
||||
shape_from_token("rounded"), "rounded")
|
||||
|
||||
check_eq("shape_to_node_shape primary",
|
||||
shape_to_node_shape("primary"), "rectangle")
|
||||
|
||||
// Lowering preserves a node id and adds style.
|
||||
let n: Map<String, Any> = make_node("svc", "Service", "primary")
|
||||
let ln: Map<String, Any> = lower_node(n)
|
||||
check_eq("lower preserves id", ln["id"], "svc")
|
||||
check_eq("lower applies primary fill", ln["style_fill"], "#0052A0")
|
||||
|
||||
// Edge lowering
|
||||
let e: Map<String, Any> = make_edge("a", "b", "dashed")
|
||||
let le: Map<String, Any> = lower_edge(e)
|
||||
check_eq("lower edge dashed line", le["line"], "dashed")
|
||||
|
||||
let e2: Map<String, Any> = make_edge("a", "b", "bidirectional")
|
||||
let le2: Map<String, Any> = lower_edge(e2)
|
||||
check_eq("lower edge bidirectional arrow", le2["arrow"], "both")
|
||||
|
||||
println("")
|
||||
let failures: String = state_get("failures")
|
||||
if str_eq(failures, "1") {
|
||||
println("arbor-core: FAILED")
|
||||
exit_program(1)
|
||||
} else {
|
||||
println("arbor-core: ok")
|
||||
}
|
||||
Reference in New Issue
Block a user