feat(ncp): add L1 index layer with path-based addressing
Implement B-Tree style index: - Path to CID mapping (exact lookup) - List by prefix (directory listing) - Glob pattern matching (* and ? wildcards) - Insert, remove, paths operations Supports hierarchical addressing: /agents/frankie/tasks Refs RFC-NCP-001 L1 Index Developed with Frankie assistance
This commit is contained in:
parent
f22390552e
commit
514e521abe
|
|
@ -0,0 +1,114 @@
|
||||||
|
## l1_index.nim: L1 Index Layer for NCP
|
||||||
|
## B-Tree index for path-based addressing
|
||||||
|
## RFC-NCP-001 Implementation
|
||||||
|
|
||||||
|
import std/[tables, sequtils, algorithm, strutils]
|
||||||
|
import types
|
||||||
|
|
||||||
|
## Index Entry: Maps path to CID
|
||||||
|
type IndexEntry* = object
|
||||||
|
path*: string ## Hierarchical path (e.g., "/agents/frankie/tasks")
|
||||||
|
cid*: CID ## Content identifier
|
||||||
|
timestamp*: int64 ## When indexed
|
||||||
|
|
||||||
|
## B-Tree Node (simplified for prototype)
|
||||||
|
type BTreeNode* = object
|
||||||
|
isLeaf*: bool
|
||||||
|
keys*: seq[string] ## Paths
|
||||||
|
values*: seq[CID] ## CIDs
|
||||||
|
children*: seq[int] ## Child node indices (for internal nodes)
|
||||||
|
|
||||||
|
## L1 Index Handle
|
||||||
|
type L1Index* = object
|
||||||
|
entries*: Table[string, IndexEntry] ## Path -> Entry (simplified B-Tree)
|
||||||
|
root*: string
|
||||||
|
|
||||||
|
proc initL1Index*(): L1Index =
|
||||||
|
## Initialize empty L1 Index
|
||||||
|
result.entries = initTable[string, IndexEntry]()
|
||||||
|
result.root = "/"
|
||||||
|
|
||||||
|
## Insert or update path -> CID mapping
|
||||||
|
proc insert*(index: var L1Index, path: string, cid: CID, timestamp: int64 = 0) =
|
||||||
|
## Index a path to CID mapping
|
||||||
|
index.entries[path] = IndexEntry(
|
||||||
|
path: path,
|
||||||
|
cid: cid,
|
||||||
|
timestamp: if timestamp == 0: getTime().toUnix() else: timestamp
|
||||||
|
)
|
||||||
|
|
||||||
|
## Lookup CID by exact path
|
||||||
|
proc lookup*(index: L1Index, path: string): Option[CID] =
|
||||||
|
## Find CID by exact path
|
||||||
|
if index.entries.hasKey(path):
|
||||||
|
return some(index.entries[path].cid)
|
||||||
|
return none(CID)
|
||||||
|
|
||||||
|
## List all paths under a prefix (directory listing)
|
||||||
|
proc list*(index: L1Index, prefix: string): seq[string] =
|
||||||
|
## List all paths starting with prefix
|
||||||
|
result = @[]
|
||||||
|
for path in index.entries.keys:
|
||||||
|
if path.startsWith(prefix):
|
||||||
|
result.add(path)
|
||||||
|
result.sort()
|
||||||
|
|
||||||
|
## Find paths matching glob pattern (simplified)
|
||||||
|
proc glob*(index: L1Index, pattern: string): seq[string] =
|
||||||
|
## Find paths matching pattern
|
||||||
|
## Supports: * (any chars), ? (single char)
|
||||||
|
result = @[]
|
||||||
|
for path in index.entries.keys:
|
||||||
|
# Simple glob matching (can be improved)
|
||||||
|
if matchGlob(path, pattern):
|
||||||
|
result.add(path)
|
||||||
|
result.sort()
|
||||||
|
|
||||||
|
## Simple glob matcher
|
||||||
|
proc matchGlob*(s, pattern: string): bool =
|
||||||
|
## Match string against glob pattern
|
||||||
|
var sIdx = 0
|
||||||
|
var pIdx = 0
|
||||||
|
|
||||||
|
while pIdx < pattern.len:
|
||||||
|
if pattern[pIdx] == '*':
|
||||||
|
# Match any sequence
|
||||||
|
if pIdx == pattern.len - 1:
|
||||||
|
return true # * at end matches everything
|
||||||
|
# Find next char after *
|
||||||
|
let nextChar = pattern[pIdx + 1]
|
||||||
|
while sIdx < s.len and s[sIdx] != nextChar:
|
||||||
|
sIdx.inc
|
||||||
|
pIdx += 2
|
||||||
|
elif pattern[pIdx] == '?':
|
||||||
|
# Match single char
|
||||||
|
if sIdx >= s.len:
|
||||||
|
return false
|
||||||
|
sIdx.inc
|
||||||
|
pIdx.inc
|
||||||
|
else:
|
||||||
|
# Match literal
|
||||||
|
if sIdx >= s.len or s[sIdx] != pattern[pIdx]:
|
||||||
|
return false
|
||||||
|
sIdx.inc
|
||||||
|
pIdx.inc
|
||||||
|
|
||||||
|
return sIdx == s.len
|
||||||
|
|
||||||
|
## Delete path from index
|
||||||
|
proc remove*(index: var L1Index, path: string): bool =
|
||||||
|
## Remove path from index
|
||||||
|
if index.entries.hasKey(path):
|
||||||
|
index.entries.del(path)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
## Get all indexed paths
|
||||||
|
proc paths*(index: L1Index): seq[string] =
|
||||||
|
## Return all indexed paths (sorted)
|
||||||
|
result = toSeq(index.entries.keys)
|
||||||
|
result.sort()
|
||||||
|
|
||||||
|
## Export
|
||||||
|
export L1Index, IndexEntry
|
||||||
|
export initL1Index, insert, lookup, list, glob, remove, paths, matchGlob
|
||||||
Loading…
Reference in New Issue