nx_teacher_lesson.nx source
↩ module page · 249 lines · 11087 B
1// nx_teacher.nx -- the NISHI TEACHER: turn a work-session into a structured,
2// machine-AND-human-educatable LESSON, validate it is S-class, and organize+log it.
3//
4// WHY (operator 2026-06-17): "take our data from [what] we did meaningful for the nishi
5// team and my growth to learn -- machine and human educatable... build a nishi teacher
6// role... organize and log what we need to practice, takeaways, etc."
7//
8// GROUNDED IN LEARNING SCIENCE (Bjork "desirable difficulties", cited evidence in
9// knowledge/registry/teaching_evidence.tsv): a lesson only TRANSFERS if it has
10// - CONTEXT concrete situation (concrete-before-abstract)
11// - TAKEAWAYS the S-class abstractions
12// - CARDINALS durable reusable rules (the MACHINE-applicable principles) -> cardinal log
13// - DRILLS retrieval-practice prompts (the testing effect) -> drill log
14// - EVIDENCE cited provenance ("we do things with evidence & science, ALWAYS")
15// A lesson missing any of these is NOT S-class -> RED (the teacher's liar-kill / publish-gate).
16//
17// The lesson is a markdown file (so the EXISTING wiki ingestor auto-discovers it as
18// /wiki/<slug>); the teacher additionally EXTRACTS the machine-readable structure
19// (cardinals + drills) into registries the Nishi team / spore can ingest. Two audiences,
20// one source. DRY: reuses nx_syscalls only; no new primitives.
21//
22// genealogy_id: nishi_teacher_lesson_capture
23// lineage_id: markdown_section_parse + cardinal_drill_extract + sclass_publish_gate
24// license_tier: ORIGINAL
25import "nx_syscalls.nx"
26
27// verdict codes
28const NX_TEACH_OK: i64 = 0
29const NX_TEACH_NO_FILE: i64 = 1
30const NX_TEACH_NO_TITLE: i64 = 2
31const NX_TEACH_MISSING: i64 = 3 // a required section is absent
32
33// does body[off..] start with the NUL-terminated literal lit (bounded by n)?
34func mem_eq(body: *u8, off: i64, n: i64, lit: *u8) -> i64 {
35 var i: i64 = 0
36 while lit[i] != (0 as u8) {
37 if off + i >= n { return 0 }
38 if body[off + i] != lit[i] { return 0 }
39 i = i + 1
40 }
41 return 1
42}
43
44// find a "## <NAME>" section header at a LINE START. Returns 1 if found and sets
45// cstart (first byte after that header line) and cend (start of the next "## " line, or n).
46func sec_bounds(body: *u8, n: i64, marker: *u8, out: *i64) -> i64 {
47 var i: i64 = 0
48 var found: i64 = 0 - 1
49 while i < n {
50 var at_line_start: i64 = 0
51 if i == 0 { at_line_start = 1 }
52 if i > 0 { if body[i - 1] == (10 as u8) { at_line_start = 1 } }
53 if at_line_start == 1 {
54 if mem_eq(body, i, n, marker) == 1 { found = i; i = n }
55 }
56 if i < n { i = i + 1 }
57 }
58 if found < 0 { return 0 }
59 // content starts after the header line's newline
60 var c: i64 = found
61 while c < n { if body[c] == (10 as u8) { c = c + 1; out[0] = c; c = n } else { c = c + 1 } }
62 if out[0] < found { out[0] = n }
63 // content ends at next line-start "## "
64 var e: i64 = out[0]
65 var cend: i64 = n
66 while e < n {
67 if e > 0 { if body[e - 1] == (10 as u8) { if mem_eq(body, e, n, "## \x00" as *u8) == 1 { cend = e; e = n } } }
68 if e < n { e = e + 1 }
69 }
70 out[1] = cend
71 return 1
72}
73
74// count bullet lines ("- " at a line start) within [start,end)
75func count_bullets(body: *u8, start: i64, end: i64) -> i64 {
76 var i: i64 = start
77 var c: i64 = 0
78 while i < end {
79 var ls: i64 = 0
80 if i == start { ls = 1 }
81 if i > start { if body[i - 1] == (10 as u8) { ls = 1 } }
82 if ls == 1 { if mem_eq(body, i, end, "- \x00" as *u8) == 1 { c = c + 1 } }
83 i = i + 1
84 }
85 return c
86}
87
88// append each bullet line's TEXT (after "- ") as "lesson_id<TAB>text\n" to fd.
89func emit_bullets(fd: i64, lesson_id: *u8, body: *u8, start: i64, end: i64) -> i64 {
90 var i: i64 = start
91 while i < end {
92 var ls: i64 = 0
93 if i == start { ls = 1 }
94 if i > start { if body[i - 1] == (10 as u8) { ls = 1 } }
95 if ls == 1 {
96 if mem_eq(body, i, end, "- \x00" as *u8) == 1 {
97 // write id + TAB
98 var k: i64 = 0
99 while lesson_id[k] != (0 as u8) { k = k + 1 }
100 sys_write(fd, lesson_id, k)
101 sys_write(fd, "\t\x00" as *u8, 1)
102 // write the bullet text (skip "- ") to end of line
103 var j: i64 = i + 2
104 while j < end { if body[j] == (10 as u8) { j = end } else { sys_write(fd, (body as i64 + j) as *u8, 1); j = j + 1 } }
105 sys_write(fd, "\n\x00" as *u8, 1)
106 }
107 }
108 i = i + 1
109 }
110 return 0
111}
112
113func tp(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
114func tn(v: i64) -> i64 {
115 let bb: *u8 = sys_mmap(28); var m: i64 = v
116 if m < 0 { sys_write(1, "-\x00" as *u8, 1); m = 0 - m }
117 let t: *u8 = sys_mmap(28); var k: i64 = 0
118 if m == 0 { t[0] = 48 as u8; k = 1 }
119 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
120 var i: i64 = 0
121 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
122 sys_write(1, bb, k); return 0
123}
124
125// EMIT the curriculum index wiki page from nishi_lessons.tsv (the "structurer": one /wiki
126// page that organizes every logged lesson, regenerated from the registry -- emit, never hand-write).
127func nx_teacher_emit_index(out_path: *u8) -> i64 {
128 let szp: *i64 = sys_mmap(16) as *i64
129 let body: *u8 = sys_read_file("knowledge/registry/nishi_lessons.tsv\x00" as *u8, szp)
130 let ofd: i64 = sys_openat_wr(out_path, O_WRONLY_CT)
131 if ofd < 0 { return 0 - 1 }
132 let hdr: *u8 = "# Nishi Lessons (curriculum index)\n\nAuto-emitted by the Nishi Teacher from nishi_lessons.tsv -- each row is a validated S-class lesson (has context, takeaways, cardinals, drills, cited evidence). Machine-readable cardinals + drills live in nishi_cardinals.tsv / nishi_drills.tsv for the team and spore to ingest.\n\n## Lessons\n\n\x00" as *u8
133 var h: i64 = 0
134 while hdr[h] != (0 as u8) { h = h + 1 }
135 sys_write(ofd, hdr, h)
136 if (body as i64) != 0 {
137 let n: i64 = szp[0]
138 var i: i64 = 0
139 while i < n {
140 // find id end (first TAB) and line end (newline) for this line
141 var idend: i64 = 0 - 1
142 var lineend: i64 = n
143 var s: i64 = i
144 while s < n {
145 if body[s] == (10 as u8) { lineend = s; s = n }
146 else {
147 if body[s] == (9 as u8) { if idend < 0 { idend = s } }
148 s = s + 1
149 }
150 }
151 if idend < 0 { idend = lineend }
152 // "- [<id>](/wiki/<id>) <rest of line after tab>\n"
153 sys_write(ofd, "- [\x00" as *u8, 3)
154 var a: i64 = i
155 while a < idend { sys_write(ofd, (body as i64 + a) as *u8, 1); a = a + 1 }
156 sys_write(ofd, "](/wiki/\x00" as *u8, 8)
157 a = i
158 while a < idend { sys_write(ofd, (body as i64 + a) as *u8, 1); a = a + 1 }
159 sys_write(ofd, ") \x00" as *u8, 2)
160 var b: i64 = idend
161 if b < lineend { if body[b] == (9 as u8) { b = b + 1 } }
162 while b < lineend { sys_write(ofd, (body as i64 + b) as *u8, 1); b = b + 1 }
163 sys_write(ofd, "\n\x00" as *u8, 1)
164 i = lineend + 1
165 }
166 }
167 sys_close(ofd)
168 return 0
169}
170
171// write a decimal number to a file descriptor
172func tn_fd(fd: i64, v: i64) -> i64 {
173 let bb: *u8 = sys_mmap(28); var m: i64 = v
174 if m < 0 { sys_write(fd, "-\x00" as *u8, 1); m = 0 - m }
175 let t: *u8 = sys_mmap(28); var k: i64 = 0
176 if m == 0 { t[0] = 48 as u8; k = 1 }
177 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
178 var i: i64 = 0
179 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
180 sys_write(fd, bb, k); return 0
181}
182
183// INGEST one lesson markdown file. Validate required sections, extract cardinals+drills to
184// registries, log the lesson. Returns NX_TEACH_OK (green) or a negative verdict code.
185func nx_teacher_ingest(path_z: *u8, lesson_id: *u8, domain: *u8) -> i64 {
186 let szp: *i64 = sys_mmap(16) as *i64
187 let body: *u8 = sys_read_file(path_z, szp)
188 if (body as i64) == 0 { return 0 - NX_TEACH_NO_FILE }
189 let n: i64 = szp[0]
190 // require a "# " title at the very top
191 if mem_eq(body, 0, n, "# \x00" as *u8) == 0 { return 0 - NX_TEACH_NO_TITLE }
192
193 let ob: *i64 = sys_mmap(16) as *i64
194 // required sections
195 var ok: i64 = 1
196 if sec_bounds(body, n, "## CONTEXT\x00" as *u8, ob) == 0 { ok = 0; tp(" MISSING ## CONTEXT\n\x00" as *u8) }
197 if sec_bounds(body, n, "## TAKEAWAYS\x00" as *u8, ob) == 0 { ok = 0; tp(" MISSING ## TAKEAWAYS\n\x00" as *u8) }
198 if sec_bounds(body, n, "## EVIDENCE\x00" as *u8, ob) == 0 { ok = 0; tp(" MISSING ## EVIDENCE\n\x00" as *u8) }
199
200 // CARDINALS: required + extract
201 var ncard: i64 = 0
202 if sec_bounds(body, n, "## CARDINALS\x00" as *u8, ob) == 0 { ok = 0; tp(" MISSING ## CARDINALS\n\x00" as *u8) }
203 if ok == 1 {
204 sec_bounds(body, n, "## CARDINALS\x00" as *u8, ob)
205 ncard = count_bullets(body, ob[0], ob[1])
206 let cfd: i64 = sys_openat_append("knowledge/registry/nishi_cardinals.tsv\x00" as *u8, 0x1a4)
207 emit_bullets(cfd, lesson_id, body, ob[0], ob[1])
208 if cfd > 0 { sys_close(cfd) }
209 }
210 // DRILLS: required + extract
211 var ndrill: i64 = 0
212 if sec_bounds(body, n, "## DRILLS\x00" as *u8, ob) == 0 { ok = 0; tp(" MISSING ## DRILLS\n\x00" as *u8) }
213 if ok == 1 {
214 sec_bounds(body, n, "## DRILLS\x00" as *u8, ob)
215 ndrill = count_bullets(body, ob[0], ob[1])
216 let dfd: i64 = sys_openat_append("knowledge/registry/nishi_drills.tsv\x00" as *u8, 0x1a4)
217 emit_bullets(dfd, lesson_id, body, ob[0], ob[1])
218 if dfd > 0 { sys_close(dfd) }
219 }
220 var nev: i64 = 0
221 if sec_bounds(body, n, "## EVIDENCE\x00" as *u8, ob) == 1 { nev = count_bullets(body, ob[0], ob[1]) }
222
223 if ok == 0 { return 0 - NX_TEACH_MISSING }
224
225 // log the lesson row: id<TAB>domain<TAB>cards<TAB>drills<TAB>evidence<TAB>bytes<TAB>GREEN
226 let lfd: i64 = sys_openat_append("knowledge/registry/nishi_lessons.tsv\x00" as *u8, 0x1a4)
227 if lfd > 0 {
228 var k: i64 = 0
229 while lesson_id[k] != (0 as u8) { k = k + 1 }
230 sys_write(lfd, lesson_id, k)
231 sys_write(lfd, "\t\x00" as *u8, 1)
232 k = 0
233 while domain[k] != (0 as u8) { k = k + 1 }
234 sys_write(lfd, domain, k)
235 // counts via a tiny number writer
236 sys_write(lfd, "\tcards=\x00" as *u8, 7)
237 tn_fd(lfd, ncard)
238 sys_write(lfd, "\tdrills=\x00" as *u8, 8)
239 tn_fd(lfd, ndrill)
240 sys_write(lfd, "\tevidence=\x00" as *u8, 10)
241 tn_fd(lfd, nev)
242 sys_write(lfd, "\tbytes=\x00" as *u8, 7)
243 tn_fd(lfd, n)
244 sys_write(lfd, "\tverdict=GREEN\n\x00" as *u8, 15)
245 sys_close(lfd)
246 }
247 tp(" ingested cards=\x00" as *u8); tn(ncard); tp(" drills=\x00" as *u8); tn(ndrill); tp(" evidence=\x00" as *u8); tn(nev); tp(" bytes=\x00" as *u8); tn(n); tp("\n\x00" as *u8)
248 return NX_TEACH_OK
249}