code wiki / wiki / nx_wiki_projects.nx

nx_wiki_projects.nx source

↩ module page · 291 lines · 15774 B

1// nx_wiki_projects.nx -- the Nishi PM's LIVE project discovery + organization. 2// 3// At render time the PM queries the Gitea API over the sovereign HTTP client 4// (git on 192.168.8.227:3003, reachable from both the dev host and the NAS), 5// classifies each repo into its meta-project (the project model), and renders 6// /wiki/projects. Self-updating, no curation, no shell. v1 surfaces the live 7// repo inventory grouped by meta-project; the project model's richer slots 8// (purpose / ROI / progress / boundaries / meta-sub-cousin relationships) 9// enrich on top next. 10// 11// PROJECT MODEL (DITA-map composition + Diataxis/Divio purpose-clarity): a 12// project has a clear point + logical boundaries; meta-projects compose subs; 13// type is physical and/or digital. v1 derives the meta grouping from repo 14// naming (the auto-classifier below). 15// 16// Status: V1. 2026-05-30. 17 18import "nx_syscalls.nx" 19import "wiki/nx_wiki_doc_render.nx" 20import "nx_http_client.nx" 21 22const NX_PJ_OK: i64 = 0 23const NX_PJ_ERR: i64 = 3100 24const NX_PJ_RENDER_CAP: i64 = 262144 25const NX_PJ_SCRATCH_CAP: i64 = 4096 26const NX_PJ_GITEA_CAP: i64 = 262144 27const NX_PJ_MAX_REPOS: i64 = 200 28const NX_PJ_METAS: i64 = 7 29const NX_PJ_STRLEN_CAP: i64 = 65536 30const NX_PJ_PUTZ_CAP: i64 = 1048576 31 32func nx_pj_strlen(s: *u8) -> i64 { 33 if (s as i64) == 0 { return 0 } 34 var n: i64 = 0 35 while n < NX_PJ_STRLEN_CAP { if s[n] == (0 as u8) { return n } n = n + 1 } 36 return NX_PJ_STRLEN_CAP 37} 38func nx_pj_put_z(dst: *u8, off: *i64, cap: i64, s: *u8) -> i64 { 39 var n: i64 = 0 40 while n < NX_PJ_PUTZ_CAP { 41 if s[n] == (0 as u8) { 42 if off[0] + n > cap { return 0 - NX_PJ_ERR } 43 var i: i64 = 0 44 while i < n { dst[off[0] + i] = s[i]; i = i + 1 } 45 off[0] = off[0] + n 46 return NX_PJ_OK 47 } 48 n = n + 1 49 } 50 return 0 - NX_PJ_ERR 51} 52// first index >= start where buf[i..i+pat_n) == pat, else -1 53func nx_pj_find_from(buf: *u8, n: i64, start: i64, pat: *u8, pat_n: i64) -> i64 { 54 if pat_n <= 0 { return 0 - 1 } 55 var i: i64 = start 56 let last: i64 = n - pat_n 57 while i <= last { 58 var j: i64 = 0 59 var ok: i64 = 1 60 while j < pat_n { if buf[i + j] != pat[j] { ok = 0; j = pat_n } else { j = j + 1 } } 61 if ok == 1 { return i } 62 i = i + 1 63 } 64 return 0 - 1 65} 66// does hay[hoff..hoff+hlen) contain needle? 67func nx_pj_contains(hay: *u8, hoff: i64, hlen: i64, needle: *u8, nlen: i64) -> i64 { 68 if nlen <= 0 { return 0 } 69 if hlen < nlen { return 0 } 70 var i: i64 = hoff 71 let last: i64 = hoff + hlen - nlen 72 while i <= last { 73 var j: i64 = 0 74 var ok: i64 = 1 75 while j < nlen { if hay[i + j] != needle[j] { ok = 0; j = nlen } else { j = j + 1 } } 76 if ok == 1 { return 1 } 77 i = i + 1 78 } 79 return 0 80} 81// classify a repo full_name (buf[off..off+len)) into a meta-project 0..6. 82// Ordered keyword rules (data-driven precedence): site -> web; game -> games; 83// home/garden/health kw; ai/software kw; nishi- -> stack; physical kw; else other. 84func nx_pj_classify(buf: *u8, off: i64, len: i64) -> i64 { 85 if nx_pj_contains(buf, off, len, "site-" as *u8, 5) == 1 { return 1 } 86 if nx_pj_contains(buf, off, len, "game" as *u8, 4) == 1 { return 3 } 87 if nx_pj_contains(buf, off, len, "dogowar" as *u8, 7) == 1 { return 3 } 88 if nx_pj_contains(buf, off, len, "greenhouse" as *u8, 10) == 1 { return 4 } 89 if nx_pj_contains(buf, off, len, "seedbank" as *u8, 8) == 1 { return 4 } 90 if nx_pj_contains(buf, off, len, "kratky" as *u8, 6) == 1 { return 4 } 91 if nx_pj_contains(buf, off, len, "soil" as *u8, 4) == 1 { return 4 } 92 if nx_pj_contains(buf, off, len, "multivitamin" as *u8, 12) == 1 { return 4 } 93 if nx_pj_contains(buf, off, len, "whey" as *u8, 4) == 1 { return 4 } 94 if nx_pj_contains(buf, off, len, "healthy-home" as *u8, 12) == 1 { return 4 } 95 if nx_pj_contains(buf, off, len, "toolkit" as *u8, 7) == 1 { return 5 } 96 if nx_pj_contains(buf, off, len, "companion" as *u8, 9) == 1 { return 5 } 97 if nx_pj_contains(buf, off, len, "llama" as *u8, 5) == 1 { return 5 } 98 if nx_pj_contains(buf, off, len, "manga" as *u8, 5) == 1 { return 5 } 99 if nx_pj_contains(buf, off, len, "synapse" as *u8, 7) == 1 { return 5 } 100 if nx_pj_contains(buf, off, len, "element" as *u8, 7) == 1 { return 5 } 101 if nx_pj_contains(buf, off, len, "inventree" as *u8, 9) == 1 { return 5 } 102 if nx_pj_contains(buf, off, len, "kohya" as *u8, 5) == 1 { return 5 } 103 if nx_pj_contains(buf, off, len, "elder-ai" as *u8, 8) == 1 { return 5 } 104 if nx_pj_contains(buf, off, len, "local-ad" as *u8, 8) == 1 { return 5 } 105 if nx_pj_contains(buf, off, len, "nishivideo" as *u8, 10) == 1 { return 0 } 106 if nx_pj_contains(buf, off, len, "nishi-" as *u8, 6) == 1 { return 0 } 107 if nx_pj_contains(buf, off, len, "tie" as *u8, 3) == 1 { return 2 } 108 if nx_pj_contains(buf, off, len, "dog" as *u8, 3) == 1 { return 2 } 109 if nx_pj_contains(buf, off, len, "brake" as *u8, 5) == 1 { return 2 } 110 if nx_pj_contains(buf, off, len, "sodastream" as *u8, 10) == 1 { return 2 } 111 if nx_pj_contains(buf, off, len, "anchor" as *u8, 6) == 1 { return 2 } 112 if nx_pj_contains(buf, off, len, "massage" as *u8, 7) == 1 { return 2 } 113 if nx_pj_contains(buf, off, len, "spatchcock" as *u8, 10) == 1 { return 2 } 114 if nx_pj_contains(buf, off, len, "climb" as *u8, 5) == 1 { return 2 } 115 if nx_pj_contains(buf, off, len, "net-pot" as *u8, 7) == 1 { return 2 } 116 if nx_pj_contains(buf, off, len, "grounding" as *u8, 9) == 1 { return 2 } 117 if nx_pj_contains(buf, off, len, "armor" as *u8, 5) == 1 { return 2 } 118 if nx_pj_contains(buf, off, len, "lego" as *u8, 4) == 1 { return 2 } 119 if nx_pj_contains(buf, off, len, "omniphobic" as *u8, 10) == 1 { return 2 } 120 if nx_pj_contains(buf, off, len, "cleaner" as *u8, 7) == 1 { return 2 } 121 return 6 122} 123func nx_pj_meta_name(m: i64) -> *u8 { 124 var r: *u8 = "Other" as *u8 125 if m == 0 { r = "Sovereign Nishi Stack" as *u8 } 126 if m == 1 { r = "Family &amp; Business Web" as *u8 } 127 if m == 2 { r = "Physical Inventions" as *u8 } 128 if m == 3 { r = "Games" as *u8 } 129 if m == 4 { r = "Home / Garden / Health" as *u8 } 130 if m == 5 { r = "AI / Software / Infra" as *u8 } 131 return r 132} 133func nx_pj_emit_int(ctx: *NxWikiDocCtx, v: i64) -> i64 { 134 if v == 0 { return nx_wiki_doc_write_byte(ctx, 0x30) } 135 if v < 0 { return nx_wiki_doc_write_byte(ctx, 0x2D) } 136 let t: *u8 = sys_mmap(32) 137 var x: i64 = v 138 var k: i64 = 0 139 while x > 0 { t[k] = (0x30 + (x % 10)) as u8; x = x / 10; k = k + 1 } 140 var j: i64 = k - 1 141 var rc: i64 = 0 142 while j >= 0 { rc = nx_wiki_doc_write_byte(ctx, t[j] as i64); if rc != NX_WIKI_DOC_OK { return rc } j = j - 1 } 143 return NX_PJ_OK 144} 145func nx_pj_finish_http(ctx: *NxWikiDocCtx, resp_buf: *u8, resp_cap: i64, out_resp_n: *i64) -> i64 { 146 let body_len: i64 = ctx.out_off 147 let off: *i64 = (sys_mmap(8)) as *i64 148 off[0] = 0 149 let rc1: i64 = nx_pj_put_z(resp_buf, off, resp_cap, 150 "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8) 151 if rc1 != NX_PJ_OK { return rc1 } 152 var bl: i64 = body_len 153 if bl == 0 { if off[0] >= resp_cap { return 0 - NX_PJ_ERR } resp_buf[off[0]] = 0x30 as u8; off[0] = off[0] + 1 } 154 if bl > 0 { 155 let tmp: *u8 = sys_mmap(32) 156 var k: i64 = 0 157 while bl > 0 { if k >= 32 { return 0 - NX_PJ_ERR } tmp[k] = (0x30 + (bl % 10)) as u8; bl = bl / 10; k = k + 1 } 158 var j: i64 = k - 1 159 while j >= 0 { if off[0] >= resp_cap { return 0 - NX_PJ_ERR } resp_buf[off[0]] = tmp[j]; off[0] = off[0] + 1; j = j - 1 } 160 } 161 let rc2: i64 = nx_pj_put_z(resp_buf, off, resp_cap, "\r\n\r\n" as *u8) 162 if rc2 != NX_PJ_OK { return rc2 } 163 if off[0] + body_len > resp_cap { return 0 - NX_PJ_ERR } 164 var bi: i64 = 0 165 while bi < body_len { resp_buf[off[0] + bi] = ctx.out_buf[bi]; bi = bi + 1 } 166 off[0] = off[0] + body_len 167 out_resp_n[0] = off[0] 168 return NX_PJ_OK 169} 170 171// ===== GET /wiki/projects ================================================= 172func nx_wiki_projects_handle(resp_buf: *u8, resp_cap: i64, out_resp_n: *i64) -> i64 { 173 if (resp_buf as i64) == 0 { return 0 - NX_PJ_ERR } 174 if (out_resp_n as i64) == 0 { return 0 - NX_PJ_ERR } 175 out_resp_n[0] = 0 176 177 // Live query: Gitea API over the sovereign HTTP client. 178 let addr: *u8 = sys_mmap(16) 179 nx_http_client_sockaddr_ipv4(addr, 192, 168, 8, 227, 3003) 180 let gbuf: *u8 = sys_mmap(NX_PJ_GITEA_CAP) 181 let vb: *i64 = (sys_mmap(8)) as *i64 182 vb[0] = 0 183 let gpath: *u8 = "/api/v1/repos/search?limit=100" as *u8 184 let ghost: *u8 = "192.168.8.227" as *u8 185 let gn: i64 = nx_http_client_get(addr, gpath, nx_pj_strlen(gpath), ghost, nx_pj_strlen(ghost), gbuf, NX_PJ_GITEA_CAP, vb) 186 187 let render_buf: *u8 = sys_mmap(NX_PJ_RENDER_CAP) 188 let scratch: *u8 = sys_mmap(NX_PJ_SCRATCH_CAP) 189 let ctx: *NxWikiDocCtx = (sys_mmap(512)) as *NxWikiDocCtx 190 let srcph: *u8 = "projects" as *u8 191 let rc_ci: i64 = nx_wiki_doc_ctx_init(ctx, render_buf, NX_PJ_RENDER_CAP, 192 srcph, nx_pj_strlen(srcph), 193 0 as *u8, 0, 0, scratch, NX_PJ_SCRATCH_CAP) 194 if rc_ci != NX_WIKI_DOC_OK { return rc_ci } 195 let title: *u8 = "Nishi Projects" as *u8 196 let rc_h: i64 = nx_wiki_doc_write_header(ctx, title, nx_pj_strlen(title)) 197 if rc_h != NX_WIKI_DOC_OK { return rc_h } 198 var rc: i64 = 0 199 rc = nx_wiki_doc_write_z(ctx, "<h1>Projects</h1><p class=\"status\">The Nishi Project Manager discovers every project LIVE from Gitea (git.nishifamily.com) and organizes them into their logical meta-projects. Executive sponsor: elderwesto. Project model: a clear point + logical boundaries, physical and/or digital, composing into meta/sub/cousin. v1 lists the live inventory grouped; purpose / ROI / progress enrich next.</p>" as *u8) 200 if rc != NX_WIKI_DOC_OK { return rc } 201 202 if gn <= 0 { 203 rc = nx_wiki_doc_write_z(ctx, "<div class=\"informative\"><p>Gitea is not answering right now, so the live inventory is unavailable. The meta-project structure the PM organizes into: Sovereign Nishi Stack &middot; Family &amp; Business Web &middot; Physical Inventions &middot; Games &middot; Home / Garden / Health &middot; AI / Software / Infra.</p></div>" as *u8) 204 if rc != NX_WIKI_DOC_OK { return rc } 205 } else { 206 let r_off: *i64 = (sys_mmap(NX_PJ_MAX_REPOS * 8)) as *i64 207 let r_len: *i64 = (sys_mmap(NX_PJ_MAX_REPOS * 8)) as *i64 208 let r_meta: *i64 = (sys_mmap(NX_PJ_MAX_REPOS * 8)) as *i64 209 let r_doff: *i64 = (sys_mmap(NX_PJ_MAX_REPOS * 8)) as *i64 210 let r_dlen: *i64 = (sys_mmap(NX_PJ_MAX_REPOS * 8)) as *i64 211 let counts: *i64 = (sys_mmap(NX_PJ_METAS * 8)) as *i64 212 var ci: i64 = 0 213 while ci < NX_PJ_METAS { counts[ci] = 0; ci = ci + 1 } 214 var pos: i64 = 0 215 var cnt: i64 = 0 216 var go: i64 = 1 217 while go == 1 { 218 let f: i64 = nx_pj_find_from(gbuf, gn, pos, "\"full_name\":\"" as *u8, 13) 219 if f < 0 { go = 0 } else { 220 let no: i64 = f + 13 221 let ne: i64 = nx_pj_find_from(gbuf, gn, no, "\"" as *u8, 1) 222 if ne < 0 { go = 0 } else { 223 // require a real "owner/repo" full_name -- skips the nested 224 // owner object's empty "full_name":"" that also matches. 225 var hasslash: i64 = 0 226 var sp: i64 = no 227 while sp < ne { if gbuf[sp] == (0x2f as u8) { hasslash = 1; sp = ne } else { sp = sp + 1 } } 228 if hasslash == 1 { if cnt < NX_PJ_MAX_REPOS { 229 r_off[cnt] = no 230 r_len[cnt] = ne - no 231 r_meta[cnt] = nx_pj_classify(gbuf, no, ne - no) 232 // Purpose = the repo description, which the JSON lists 233 // right after full_name (guard: must be nearby). 234 var doff: i64 = 0 235 var dlen: i64 = 0 236 let df: i64 = nx_pj_find_from(gbuf, gn, ne, "\"description\":\"" as *u8, 15) 237 if df >= 0 { if df < ne + 400 { 238 let ds: i64 = df + 15 239 let de: i64 = nx_pj_find_from(gbuf, gn, ds, "\"" as *u8, 1) 240 if de > ds { doff = ds; dlen = de - ds; if dlen > 160 { dlen = 160 } } 241 } } 242 r_doff[cnt] = doff 243 r_dlen[cnt] = dlen 244 counts[r_meta[cnt]] = counts[r_meta[cnt]] + 1 245 cnt = cnt + 1 246 } } 247 pos = ne + 1 248 if cnt >= NX_PJ_MAX_REPOS { go = 0 } 249 } 250 } 251 } 252 rc = nx_wiki_doc_write_z(ctx, "<p class=\"status\">Discovered " as *u8); if rc != NX_WIKI_DOC_OK { return rc } 253 rc = nx_pj_emit_int(ctx, cnt); if rc != NX_PJ_OK { return rc } 254 rc = nx_wiki_doc_write_z(ctx, " projects (first Gitea page; pagination for the full set is next). Each shows its Purpose (description); ROI / progress / sub-cousin relationships enrich the project model on top.</p>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 255 var m: i64 = 0 256 while m < NX_PJ_METAS { 257 rc = nx_wiki_doc_write_z(ctx, "<h2>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 258 rc = nx_wiki_doc_write_z(ctx, nx_pj_meta_name(m)); if rc != NX_WIKI_DOC_OK { return rc } 259 rc = nx_wiki_doc_write_z(ctx, " <span class=\"status\">(" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 260 rc = nx_pj_emit_int(ctx, counts[m]); if rc != NX_PJ_OK { return rc } 261 rc = nx_wiki_doc_write_z(ctx, ")</span></h2><ul>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 262 var i: i64 = 0 263 while i < cnt { 264 if r_meta[i] == m { 265 let off: i64 = r_off[i] 266 let ln: i64 = r_len[i] 267 var nstart: i64 = off 268 var p: i64 = off 269 while p < off + ln { if gbuf[p] == (0x2f as u8) { nstart = p + 1; p = off + ln } else { p = p + 1 } } 270 let nlen: i64 = (off + ln) - nstart 271 rc = nx_wiki_doc_write_z(ctx, "<li><strong>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 272 rc = nx_wiki_doc_write_escaped(ctx, ((gbuf as i64) + nstart) as *u8, nlen); if rc != NX_WIKI_DOC_OK { return rc } 273 rc = nx_wiki_doc_write_z(ctx, "</strong>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 274 if r_dlen[i] > 0 { 275 rc = nx_wiki_doc_write_z(ctx, " &mdash; " as *u8); if rc != NX_WIKI_DOC_OK { return rc } 276 rc = nx_wiki_doc_write_escaped(ctx, ((gbuf as i64) + r_doff[i]) as *u8, r_dlen[i]); if rc != NX_WIKI_DOC_OK { return rc } 277 } 278 rc = nx_wiki_doc_write_z(ctx, "</li>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 279 } 280 i = i + 1 281 } 282 rc = nx_wiki_doc_write_z(ctx, "</ul>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 283 m = m + 1 284 } 285 } 286 287 rc = nx_wiki_doc_write_z(ctx, "<p class=\"status\">Discovered live by the Nishi PM via the sovereign HTTP client (no shell). Each repo is a project node; meta/sub/cousin relationships + purpose/ROI/progress are the next enrichment.</p>" as *u8); if rc != NX_WIKI_DOC_OK { return rc } 288 let rc_f: i64 = nx_wiki_doc_write_footer(ctx) 289 if rc_f != NX_WIKI_DOC_OK { return rc_f } 290 return nx_pj_finish_http(ctx, resp_buf, resp_cap, out_resp_n) 291}