[{"id":"/root","type":"ROOT","loc":{"start":0,"end":16702,"column":{"s":0,"e":114},"line":{"s":0,"e":376}},"dim":[""],"code":null},{"id":"/root/children/0","type":"paragraph","loc":{"start":0,"end":112,"line":{"s":1,"e":1,"code":[";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }"]},"column":{"s":0,"e":112}},"dim":["","paragraph.0"],"code":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }"},{"id":"/root/children/0/children/0","type":"text","loc":{"start":0,"end":112,"line":{"s":1,"e":1,"code":[";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }"]},"column":{"s":0,"e":112}},"dim":["","paragraph.0","text.0"],"code":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }"},{"id":"/root/children/1","type":"heading","loc":{"start":114,"end":124,"line":{"s":3,"e":3,"code":["# ast-lake"]},"column":{"s":0,"e":10}},"dim":["","heading.1"],"code":"# ast-lake","symbName":"heading","symbRange":[126,211],"symbRangeL":[3,9],"outerCode":"\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)","outerHtml":"\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>"},{"id":"/root/children/1/children/0","type":"text","loc":{"start":116,"end":124,"line":{"s":3,"e":3,"code":["# ast-lake"]},"column":{"s":2,"e":10}},"dim":["","heading.1","text.0"],"code":"ast-lake"},{"id":"/root/children/2","type":"paragraph","loc":{"start":126,"end":161,"line":{"s":5,"e":5,"code":["usage: ast-lake [command] [args...]"]},"column":{"s":0,"e":35}},"dim":["","paragraph.2"],"code":"usage: ast-lake [command] [args...]"},{"id":"/root/children/2/children/0","type":"text","loc":{"start":126,"end":161,"line":{"s":5,"e":5,"code":["usage: ast-lake [command] [args...]"]},"column":{"s":0,"e":35}},"dim":["","paragraph.2","text.0"],"code":"usage: ast-lake [command] [args...]"},{"id":"/root/children/3","type":"list","loc":{"start":163,"end":211,"line":{"s":7,"e":8,"code":["- [123](http://xxx)","- [xxx](#/papers/@dashboard)"]},"column":{"s":0,"e":28}},"dim":["","list.3"],"code":"- [123](http://xxx)\n- [xxx](#/papers/@dashboard)","symbName":"list","symbRange":[213,235],"symbRangeL":[7,13],"outerCode":"- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info","outerHtml":"<ul><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>"},{"id":"/root/children/3/children/0","type":"listItem","loc":{"start":163,"end":182,"line":{"s":7,"e":7,"code":["- [123](http://xxx)"]},"column":{"s":0,"e":19}},"dim":["","list.3","listItem.0"],"code":"- [123](http://xxx)"},{"id":"/root/children/3/children/0/children/0","type":"paragraph","loc":{"start":165,"end":182,"line":{"s":7,"e":7,"code":["- [123](http://xxx)"]},"column":{"s":2,"e":19}},"dim":["","list.3","listItem.0","paragraph.0"],"code":"[123](http://xxx)"},{"id":"/root/children/3/children/0/children/0/children/0","type":"link","loc":{"start":165,"end":182,"line":{"s":7,"e":7,"code":["- [123](http://xxx)"]},"column":{"s":2,"e":19}},"dim":["","list.3","listItem.0","paragraph.0","link.0"],"code":"[123](http://xxx)","symbName":"link","symbRange":[null,null],"symbRangeL":[7,7],"outerCode":"[123](http://xxx)","outerHtml":"<p><a href=\"http://xxx\">123</a></p>"},{"id":"/root/children/3/children/0/children/0/children/0/children/0","type":"text","loc":{"start":166,"end":169,"line":{"s":7,"e":7,"code":["- [123](http://xxx)"]},"column":{"s":3,"e":6}},"dim":["","list.3","listItem.0","paragraph.0","link.0","text.0"],"code":"123"},{"id":"/root/children/3/children/1","type":"listItem","loc":{"start":183,"end":211,"line":{"s":8,"e":8,"code":["- [xxx](#/papers/@dashboard)"]},"column":{"s":0,"e":28}},"dim":["","list.3","listItem.1"],"code":"- [xxx](#/papers/@dashboard)"},{"id":"/root/children/3/children/1/children/0","type":"paragraph","loc":{"start":185,"end":211,"line":{"s":8,"e":8,"code":["- [xxx](#/papers/@dashboard)"]},"column":{"s":2,"e":28}},"dim":["","list.3","listItem.1","paragraph.0"],"code":"[xxx](#/papers/@dashboard)"},{"id":"/root/children/3/children/1/children/0/children/0","type":"link","loc":{"start":185,"end":211,"line":{"s":8,"e":8,"code":["- [xxx](#/papers/@dashboard)"]},"column":{"s":2,"e":28}},"dim":["","list.3","listItem.1","paragraph.0","link.0"],"code":"[xxx](#/papers/@dashboard)","symbName":"link","symbRange":[null,null],"symbRangeL":[8,8],"outerCode":"[xxx](#/papers/@dashboard)","outerHtml":"<p><a href=\"#/papers/@dashboard\">xxx</a></p>"},{"id":"/root/children/3/children/1/children/0/children/0/children/0","type":"text","loc":{"start":186,"end":189,"line":{"s":8,"e":8,"code":["- [xxx](#/papers/@dashboard)"]},"column":{"s":3,"e":6}},"dim":["","list.3","listItem.1","paragraph.0","link.0","text.0"],"code":"xxx"},{"id":"/root/children/4","type":"heading","loc":{"start":213,"end":225,"line":{"s":10,"e":10,"code":["## commands:"]},"column":{"s":0,"e":12}},"dim":["","heading.4"],"code":"## commands:","symbName":"heading","symbRange":[227,16701],"symbRangeL":[10,11],"outerCode":"","outerHtml":""},{"id":"/root/children/4/children/0","type":"text","loc":{"start":216,"end":225,"line":{"s":10,"e":10,"code":["## commands:"]},"column":{"s":3,"e":12}},"dim":["","heading.4","text.0"],"code":"commands:"},{"id":"/root/children/5","type":"heading","loc":{"start":227,"end":235,"line":{"s":12,"e":12,"code":["### info"]},"column":{"s":0,"e":8}},"dim":["","heading.5"],"code":"### info","symbName":"heading","symbRange":[237,268],"symbRangeL":[12,15],"outerCode":"\n- info on the state of ast-lake","outerHtml":"\n<ul><li>info on the state of ast-lake</li></ul>"},{"id":"/root/children/5/children/0","type":"text","loc":{"start":231,"end":235,"line":{"s":12,"e":12,"code":["### info"]},"column":{"s":4,"e":8}},"dim":["","heading.5","text.0"],"code":"info"},{"id":"/root/children/6","type":"list","loc":{"start":237,"end":268,"line":{"s":14,"e":14,"code":["- info on the state of ast-lake"]},"column":{"s":0,"e":31}},"dim":["","list.6"],"code":"- info on the state of ast-lake","symbName":"list","symbRange":[270,278],"symbRangeL":[14,17],"outerCode":"\n### init","outerHtml":"\n<h3>init</h3>"},{"id":"/root/children/6/children/0","type":"listItem","loc":{"start":237,"end":268,"line":{"s":14,"e":14,"code":["- info on the state of ast-lake"]},"column":{"s":0,"e":31}},"dim":["","list.6","listItem.0"],"code":"- info on the state of ast-lake"},{"id":"/root/children/6/children/0/children/0","type":"paragraph","loc":{"start":239,"end":268,"line":{"s":14,"e":14,"code":["- info on the state of ast-lake"]},"column":{"s":2,"e":31}},"dim":["","list.6","listItem.0","paragraph.0"],"code":"info on the state of ast-lake"},{"id":"/root/children/6/children/0/children/0/children/0","type":"text","loc":{"start":239,"end":268,"line":{"s":14,"e":14,"code":["- info on the state of ast-lake"]},"column":{"s":2,"e":31}},"dim":["","list.6","listItem.0","paragraph.0","text.0"],"code":"info on the state of ast-lake"},{"id":"/root/children/7","type":"heading","loc":{"start":270,"end":278,"line":{"s":16,"e":16,"code":["### init"]},"column":{"s":0,"e":8}},"dim":["","heading.7"],"code":"### init","symbName":"heading","symbRange":[280,304],"symbRangeL":[16,19],"outerCode":"\n- precreate the cache db","outerHtml":"\n<ul><li>precreate the cache db</li></ul>"},{"id":"/root/children/7/children/0","type":"text","loc":{"start":274,"end":278,"line":{"s":16,"e":16,"code":["### init"]},"column":{"s":4,"e":8}},"dim":["","heading.7","text.0"],"code":"init"},{"id":"/root/children/8","type":"list","loc":{"start":280,"end":304,"line":{"s":18,"e":18,"code":["- precreate the cache db"]},"column":{"s":0,"e":24}},"dim":["","list.8"],"code":"- precreate the cache db","symbName":"list","symbRange":[306,315],"symbRangeL":[18,21],"outerCode":"\n### reset","outerHtml":"\n<h3>reset</h3>"},{"id":"/root/children/8/children/0","type":"listItem","loc":{"start":280,"end":304,"line":{"s":18,"e":18,"code":["- precreate the cache db"]},"column":{"s":0,"e":24}},"dim":["","list.8","listItem.0"],"code":"- precreate the cache db"},{"id":"/root/children/8/children/0/children/0","type":"paragraph","loc":{"start":282,"end":304,"line":{"s":18,"e":18,"code":["- precreate the cache db"]},"column":{"s":2,"e":24}},"dim":["","list.8","listItem.0","paragraph.0"],"code":"precreate the cache db"},{"id":"/root/children/8/children/0/children/0/children/0","type":"text","loc":{"start":282,"end":304,"line":{"s":18,"e":18,"code":["- precreate the cache db"]},"column":{"s":2,"e":24}},"dim":["","list.8","listItem.0","paragraph.0","text.0"],"code":"precreate the cache db"},{"id":"/root/children/9","type":"heading","loc":{"start":306,"end":315,"line":{"s":20,"e":20,"code":["### reset"]},"column":{"s":0,"e":9}},"dim":["","heading.9"],"code":"### reset","symbName":"heading","symbRange":[317,355],"symbRangeL":[20,24],"outerCode":"\n- delete list\n- delete all cached data","outerHtml":"\n<ul><li>delete list</li><li>delete all cached data</li></ul>"},{"id":"/root/children/9/children/0","type":"text","loc":{"start":310,"end":315,"line":{"s":20,"e":20,"code":["### reset"]},"column":{"s":4,"e":9}},"dim":["","heading.9","text.0"],"code":"reset"},{"id":"/root/children/10","type":"list","loc":{"start":317,"end":355,"line":{"s":22,"e":23,"code":["- delete list","- delete all cached data"]},"column":{"s":0,"e":24}},"dim":["","list.10"],"code":"- delete list\n- delete all cached data","symbName":"list","symbRange":[357,368],"symbRangeL":[22,26],"outerCode":"- delete all cached data\n\n### cleanup","outerHtml":"<ul><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>"},{"id":"/root/children/10/children/0","type":"listItem","loc":{"start":317,"end":330,"line":{"s":22,"e":22,"code":["- delete list"]},"column":{"s":0,"e":13}},"dim":["","list.10","listItem.0"],"code":"- delete list"},{"id":"/root/children/10/children/0/children/0","type":"paragraph","loc":{"start":319,"end":330,"line":{"s":22,"e":22,"code":["- delete list"]},"column":{"s":2,"e":13}},"dim":["","list.10","listItem.0","paragraph.0"],"code":"delete list"},{"id":"/root/children/10/children/0/children/0/children/0","type":"text","loc":{"start":319,"end":330,"line":{"s":22,"e":22,"code":["- delete list"]},"column":{"s":2,"e":13}},"dim":["","list.10","listItem.0","paragraph.0","text.0"],"code":"delete list"},{"id":"/root/children/10/children/1","type":"listItem","loc":{"start":331,"end":355,"line":{"s":23,"e":23,"code":["- delete all cached data"]},"column":{"s":0,"e":24}},"dim":["","list.10","listItem.1"],"code":"- delete all cached data"},{"id":"/root/children/10/children/1/children/0","type":"paragraph","loc":{"start":333,"end":355,"line":{"s":23,"e":23,"code":["- delete all cached data"]},"column":{"s":2,"e":24}},"dim":["","list.10","listItem.1","paragraph.0"],"code":"delete all cached data"},{"id":"/root/children/10/children/1/children/0/children/0","type":"text","loc":{"start":333,"end":355,"line":{"s":23,"e":23,"code":["- delete all cached data"]},"column":{"s":2,"e":24}},"dim":["","list.10","listItem.1","paragraph.0","text.0"],"code":"delete all cached data"},{"id":"/root/children/11","type":"heading","loc":{"start":357,"end":368,"line":{"s":25,"e":25,"code":["### cleanup"]},"column":{"s":0,"e":11}},"dim":["","heading.11"],"code":"### cleanup","symbName":"heading","symbRange":[370,415],"symbRangeL":[25,28],"outerCode":"\n- remove non existent files from sqlite cache","outerHtml":"\n<ul><li>remove non existent files from sqlite cache</li></ul>"},{"id":"/root/children/11/children/0","type":"text","loc":{"start":361,"end":368,"line":{"s":25,"e":25,"code":["### cleanup"]},"column":{"s":4,"e":11}},"dim":["","heading.11","text.0"],"code":"cleanup"},{"id":"/root/children/12","type":"list","loc":{"start":370,"end":415,"line":{"s":27,"e":27,"code":["- remove non existent files from sqlite cache"]},"column":{"s":0,"e":45}},"dim":["","list.12"],"code":"- remove non existent files from sqlite cache","symbName":"list","symbRange":[417,428],"symbRangeL":[27,30],"outerCode":"\n### restart","outerHtml":"\n<h3>restart</h3>"},{"id":"/root/children/12/children/0","type":"listItem","loc":{"start":370,"end":415,"line":{"s":27,"e":27,"code":["- remove non existent files from sqlite cache"]},"column":{"s":0,"e":45}},"dim":["","list.12","listItem.0"],"code":"- remove non existent files from sqlite cache"},{"id":"/root/children/12/children/0/children/0","type":"paragraph","loc":{"start":372,"end":415,"line":{"s":27,"e":27,"code":["- remove non existent files from sqlite cache"]},"column":{"s":2,"e":45}},"dim":["","list.12","listItem.0","paragraph.0"],"code":"remove non existent files from sqlite cache"},{"id":"/root/children/12/children/0/children/0/children/0","type":"text","loc":{"start":372,"end":415,"line":{"s":27,"e":27,"code":["- remove non existent files from sqlite cache"]},"column":{"s":2,"e":45}},"dim":["","list.12","listItem.0","paragraph.0","text.0"],"code":"remove non existent files from sqlite cache"},{"id":"/root/children/13","type":"heading","loc":{"start":417,"end":428,"line":{"s":29,"e":29,"code":["### restart"]},"column":{"s":0,"e":11}},"dim":["","heading.13"],"code":"### restart","symbName":"heading","symbRange":[430,466],"symbRangeL":[29,33],"outerCode":"\n- keep list\n- delete all cached data","outerHtml":"\n<ul><li>keep list</li><li>delete all cached data</li></ul>"},{"id":"/root/children/13/children/0","type":"text","loc":{"start":421,"end":428,"line":{"s":29,"e":29,"code":["### restart"]},"column":{"s":4,"e":11}},"dim":["","heading.13","text.0"],"code":"restart"},{"id":"/root/children/14","type":"list","loc":{"start":430,"end":466,"line":{"s":31,"e":32,"code":["- keep list","- delete all cached data"]},"column":{"s":0,"e":24}},"dim":["","list.14"],"code":"- keep list\n- delete all cached data","symbName":"list","symbRange":[468,484],"symbRangeL":[31,35],"outerCode":"- delete all cached data\n\n#### reset-nodes","outerHtml":"<ul><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>"},{"id":"/root/children/14/children/0","type":"listItem","loc":{"start":430,"end":441,"line":{"s":31,"e":31,"code":["- keep list"]},"column":{"s":0,"e":11}},"dim":["","list.14","listItem.0"],"code":"- keep list"},{"id":"/root/children/14/children/0/children/0","type":"paragraph","loc":{"start":432,"end":441,"line":{"s":31,"e":31,"code":["- keep list"]},"column":{"s":2,"e":11}},"dim":["","list.14","listItem.0","paragraph.0"],"code":"keep list"},{"id":"/root/children/14/children/0/children/0/children/0","type":"text","loc":{"start":432,"end":441,"line":{"s":31,"e":31,"code":["- keep list"]},"column":{"s":2,"e":11}},"dim":["","list.14","listItem.0","paragraph.0","text.0"],"code":"keep list"},{"id":"/root/children/14/children/1","type":"listItem","loc":{"start":442,"end":466,"line":{"s":32,"e":32,"code":["- delete all cached data"]},"column":{"s":0,"e":24}},"dim":["","list.14","listItem.1"],"code":"- delete all cached data"},{"id":"/root/children/14/children/1/children/0","type":"paragraph","loc":{"start":444,"end":466,"line":{"s":32,"e":32,"code":["- delete all cached data"]},"column":{"s":2,"e":24}},"dim":["","list.14","listItem.1","paragraph.0"],"code":"delete all cached data"},{"id":"/root/children/14/children/1/children/0/children/0","type":"text","loc":{"start":444,"end":466,"line":{"s":32,"e":32,"code":["- delete all cached data"]},"column":{"s":2,"e":24}},"dim":["","list.14","listItem.1","paragraph.0","text.0"],"code":"delete all cached data"},{"id":"/root/children/15","type":"heading","loc":{"start":468,"end":484,"line":{"s":34,"e":34,"code":["#### reset-nodes"]},"column":{"s":0,"e":16}},"dim":["","heading.15"],"code":"#### reset-nodes","symbName":"heading","symbRange":[486,540],"symbRangeL":[34,38],"outerCode":"\n- cleanup only cached nodes (dev)\n- and recreated them","outerHtml":"\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>"},{"id":"/root/children/15/children/0","type":"text","loc":{"start":473,"end":484,"line":{"s":34,"e":34,"code":["#### reset-nodes"]},"column":{"s":5,"e":16}},"dim":["","heading.15","text.0"],"code":"reset-nodes"},{"id":"/root/children/16","type":"list","loc":{"start":486,"end":540,"line":{"s":36,"e":37,"code":["- cleanup only cached nodes (dev)","- and recreated them"]},"column":{"s":0,"e":20}},"dim":["","list.16"],"code":"- cleanup only cached nodes (dev)\n- and recreated them","symbName":"list","symbRange":[542,549],"symbRangeL":[36,40],"outerCode":"- and recreated them\n\n### add","outerHtml":"<ul><li>and recreated them</li></ul>\n\n<h3>add</h3>"},{"id":"/root/children/16/children/0","type":"listItem","loc":{"start":486,"end":519,"line":{"s":36,"e":36,"code":["- cleanup only cached nodes (dev)"]},"column":{"s":0,"e":33}},"dim":["","list.16","listItem.0"],"code":"- cleanup only cached nodes (dev)"},{"id":"/root/children/16/children/0/children/0","type":"paragraph","loc":{"start":488,"end":519,"line":{"s":36,"e":36,"code":["- cleanup only cached nodes (dev)"]},"column":{"s":2,"e":33}},"dim":["","list.16","listItem.0","paragraph.0"],"code":"cleanup only cached nodes (dev)"},{"id":"/root/children/16/children/0/children/0/children/0","type":"text","loc":{"start":488,"end":519,"line":{"s":36,"e":36,"code":["- cleanup only cached nodes (dev)"]},"column":{"s":2,"e":33}},"dim":["","list.16","listItem.0","paragraph.0","text.0"],"code":"cleanup only cached nodes (dev)"},{"id":"/root/children/16/children/1","type":"listItem","loc":{"start":520,"end":540,"line":{"s":37,"e":37,"code":["- and recreated them"]},"column":{"s":0,"e":20}},"dim":["","list.16","listItem.1"],"code":"- and recreated them"},{"id":"/root/children/16/children/1/children/0","type":"paragraph","loc":{"start":522,"end":540,"line":{"s":37,"e":37,"code":["- and recreated them"]},"column":{"s":2,"e":20}},"dim":["","list.16","listItem.1","paragraph.0"],"code":"and recreated them"},{"id":"/root/children/16/children/1/children/0/children/0","type":"text","loc":{"start":522,"end":540,"line":{"s":37,"e":37,"code":["- and recreated them"]},"column":{"s":2,"e":20}},"dim":["","list.16","listItem.1","paragraph.0","text.0"],"code":"and recreated them"},{"id":"/root/children/17","type":"heading","loc":{"start":542,"end":549,"line":{"s":39,"e":39,"code":["### add"]},"column":{"s":0,"e":7}},"dim":["","heading.17"],"code":"### add","symbName":"heading","symbRange":[551,1258],"symbRangeL":[39,71],"outerCode":"\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`","outerHtml":"\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>"},{"id":"/root/children/17/children/0","type":"text","loc":{"start":546,"end":549,"line":{"s":39,"e":39,"code":["### add"]},"column":{"s":4,"e":7}},"dim":["","heading.17","text.0"],"code":"add"},{"id":"/root/children/18","type":"list","loc":{"start":551,"end":768,"line":{"s":41,"e":46,"code":["- add files to list","- dbPopulateList is a temporary list file, listing absolute file paths","- dbPopulateList is usually `/tmp/ast-list.txt`","- add absolute file paths:","- note: does use `fd`. honors `.gitignore`","- usage:"]},"column":{"s":0,"e":8}},"dim":["","list.18"],"code":"- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:","symbName":"list","symbRange":[770,811],"symbRangeL":[41,51],"outerCode":"- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```","outerHtml":"<ul><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>"},{"id":"/root/children/18/children/0","type":"listItem","loc":{"start":551,"end":570,"line":{"s":41,"e":41,"code":["- add files to list"]},"column":{"s":0,"e":19}},"dim":["","list.18","listItem.0"],"code":"- add files to list"},{"id":"/root/children/18/children/0/children/0","type":"paragraph","loc":{"start":553,"end":570,"line":{"s":41,"e":41,"code":["- add files to list"]},"column":{"s":2,"e":19}},"dim":["","list.18","listItem.0","paragraph.0"],"code":"add files to list"},{"id":"/root/children/18/children/0/children/0/children/0","type":"text","loc":{"start":553,"end":570,"line":{"s":41,"e":41,"code":["- add files to list"]},"column":{"s":2,"e":19}},"dim":["","list.18","listItem.0","paragraph.0","text.0"],"code":"add files to list"},{"id":"/root/children/18/children/1","type":"listItem","loc":{"start":571,"end":641,"line":{"s":42,"e":42,"code":["- dbPopulateList is a temporary list file, listing absolute file paths"]},"column":{"s":0,"e":70}},"dim":["","list.18","listItem.1"],"code":"- dbPopulateList is a temporary list file, listing absolute file paths"},{"id":"/root/children/18/children/1/children/0","type":"paragraph","loc":{"start":573,"end":641,"line":{"s":42,"e":42,"code":["- dbPopulateList is a temporary list file, listing absolute file paths"]},"column":{"s":2,"e":70}},"dim":["","list.18","listItem.1","paragraph.0"],"code":"dbPopulateList is a temporary list file, listing absolute file paths"},{"id":"/root/children/18/children/1/children/0/children/0","type":"text","loc":{"start":573,"end":641,"line":{"s":42,"e":42,"code":["- dbPopulateList is a temporary list file, listing absolute file paths"]},"column":{"s":2,"e":70}},"dim":["","list.18","listItem.1","paragraph.0","text.0"],"code":"dbPopulateList is a temporary list file, listing absolute file paths"},{"id":"/root/children/18/children/2","type":"listItem","loc":{"start":642,"end":689,"line":{"s":43,"e":43,"code":["- dbPopulateList is usually `/tmp/ast-list.txt`"]},"column":{"s":0,"e":47}},"dim":["","list.18","listItem.2"],"code":"- dbPopulateList is usually `/tmp/ast-list.txt`"},{"id":"/root/children/18/children/2/children/0","type":"paragraph","loc":{"start":644,"end":689,"line":{"s":43,"e":43,"code":["- dbPopulateList is usually `/tmp/ast-list.txt`"]},"column":{"s":2,"e":47}},"dim":["","list.18","listItem.2","paragraph.0"],"code":"dbPopulateList is usually `/tmp/ast-list.txt`"},{"id":"/root/children/18/children/2/children/0/children/0","type":"text","loc":{"start":644,"end":670,"line":{"s":43,"e":43,"code":["- dbPopulateList is usually `/tmp/ast-list.txt`"]},"column":{"s":2,"e":28}},"dim":["","list.18","listItem.2","paragraph.0","text.0"],"code":"dbPopulateList is usually "},{"id":"/root/children/18/children/2/children/0/children/1","type":"inlineCode","loc":{"start":670,"end":689,"line":{"s":43,"e":43,"code":["- dbPopulateList is usually `/tmp/ast-list.txt`"]},"column":{"s":28,"e":47}},"dim":["","list.18","listItem.2","paragraph.0","inlineCode.1"],"code":"`/tmp/ast-list.txt`"},{"id":"/root/children/18/children/3","type":"listItem","loc":{"start":690,"end":716,"line":{"s":44,"e":44,"code":["- add absolute file paths:"]},"column":{"s":0,"e":26}},"dim":["","list.18","listItem.3"],"code":"- add absolute file paths:"},{"id":"/root/children/18/children/3/children/0","type":"paragraph","loc":{"start":692,"end":716,"line":{"s":44,"e":44,"code":["- add absolute file paths:"]},"column":{"s":2,"e":26}},"dim":["","list.18","listItem.3","paragraph.0"],"code":"add absolute file paths:"},{"id":"/root/children/18/children/3/children/0/children/0","type":"text","loc":{"start":692,"end":716,"line":{"s":44,"e":44,"code":["- add absolute file paths:"]},"column":{"s":2,"e":26}},"dim":["","list.18","listItem.3","paragraph.0","text.0"],"code":"add absolute file paths:"},{"id":"/root/children/18/children/4","type":"listItem","loc":{"start":717,"end":759,"line":{"s":45,"e":45,"code":["- note: does use `fd`. honors `.gitignore`"]},"column":{"s":0,"e":42}},"dim":["","list.18","listItem.4"],"code":"- note: does use `fd`. honors `.gitignore`"},{"id":"/root/children/18/children/4/children/0","type":"paragraph","loc":{"start":719,"end":759,"line":{"s":45,"e":45,"code":["- note: does use `fd`. honors `.gitignore`"]},"column":{"s":2,"e":42}},"dim":["","list.18","listItem.4","paragraph.0"],"code":"note: does use `fd`. honors `.gitignore`"},{"id":"/root/children/18/children/4/children/0/children/0","type":"text","loc":{"start":719,"end":734,"line":{"s":45,"e":45,"code":["- note: does use `fd`. honors `.gitignore`"]},"column":{"s":2,"e":17}},"dim":["","list.18","listItem.4","paragraph.0","text.0"],"code":"note: does use "},{"id":"/root/children/18/children/4/children/0/children/1","type":"inlineCode","loc":{"start":734,"end":738,"line":{"s":45,"e":45,"code":["- note: does use `fd`. honors `.gitignore`"]},"column":{"s":17,"e":21}},"dim":["","list.18","listItem.4","paragraph.0","inlineCode.1"],"code":"`fd`"},{"id":"/root/children/18/children/4/children/0/children/2","type":"text","loc":{"start":738,"end":747,"line":{"s":45,"e":45,"code":["- note: does use `fd`. honors `.gitignore`"]},"column":{"s":21,"e":30}},"dim":["","list.18","listItem.4","paragraph.0","text.2"],"code":". honors "},{"id":"/root/children/18/children/4/children/0/children/3","type":"inlineCode","loc":{"start":747,"end":759,"line":{"s":45,"e":45,"code":["- note: does use `fd`. honors `.gitignore`"]},"column":{"s":30,"e":42}},"dim":["","list.18","listItem.4","paragraph.0","inlineCode.3"],"code":"`.gitignore`"},{"id":"/root/children/18/children/5","type":"listItem","loc":{"start":760,"end":768,"line":{"s":46,"e":46,"code":["- usage:"]},"column":{"s":0,"e":8}},"dim":["","list.18","listItem.5"],"code":"- usage:"},{"id":"/root/children/18/children/5/children/0","type":"paragraph","loc":{"start":762,"end":768,"line":{"s":46,"e":46,"code":["- usage:"]},"column":{"s":2,"e":8}},"dim":["","list.18","listItem.5","paragraph.0"],"code":"usage:"},{"id":"/root/children/18/children/5/children/0/children/0","type":"text","loc":{"start":762,"end":768,"line":{"s":46,"e":46,"code":["- usage:"]},"column":{"s":2,"e":8}},"dim":["","list.18","listItem.5","paragraph.0","text.0"],"code":"usage:"},{"id":"/root/children/19","type":"code","loc":{"start":770,"end":811,"line":{"s":48,"e":50,"code":["```bash","ast-lake add '*.mdd' '*.json'","```"]},"column":{"s":0,"e":3}},"dim":["","code.19"],"code":"```bash\nast-lake add '*.mdd' '*.json'\n```","symbName":"code","symbRange":[813,902],"symbRangeL":[null,56],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>"},{"id":"/root/children/20","type":"list","loc":{"start":813,"end":902,"line":{"s":52,"e":55,"code":["- will add `**/*.mdd` and `*.json` to list","","- or manually:","- you may edit $dbPopulateList"]},"column":{"s":0,"e":30}},"dim":["","list.20"],"code":"- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList","symbName":"list","symbRange":[904,1226],"symbRangeL":[52,69],"outerCode":"\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```","outerHtml":"\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>"},{"id":"/root/children/20/children/0","type":"listItem","loc":{"start":813,"end":855,"line":{"s":52,"e":52,"code":["- will add `**/*.mdd` and `*.json` to list"]},"column":{"s":0,"e":42}},"dim":["","list.20","listItem.0"],"code":"- will add `**/*.mdd` and `*.json` to list"},{"id":"/root/children/20/children/0/children/0","type":"paragraph","loc":{"start":815,"end":855,"line":{"s":52,"e":52,"code":["- will add `**/*.mdd` and `*.json` to list"]},"column":{"s":2,"e":42}},"dim":["","list.20","listItem.0","paragraph.0"],"code":"will add `**/*.mdd` and `*.json` to list"},{"id":"/root/children/20/children/0/children/0/children/0","type":"text","loc":{"start":815,"end":824,"line":{"s":52,"e":52,"code":["- will add `**/*.mdd` and `*.json` to list"]},"column":{"s":2,"e":11}},"dim":["","list.20","listItem.0","paragraph.0","text.0"],"code":"will add "},{"id":"/root/children/20/children/0/children/0/children/1","type":"inlineCode","loc":{"start":824,"end":834,"line":{"s":52,"e":52,"code":["- will add `**/*.mdd` and `*.json` to list"]},"column":{"s":11,"e":21}},"dim":["","list.20","listItem.0","paragraph.0","inlineCode.1"],"code":"`**/*.mdd`"},{"id":"/root/children/20/children/0/children/0/children/2","type":"text","loc":{"start":834,"end":839,"line":{"s":52,"e":52,"code":["- will add `**/*.mdd` and `*.json` to list"]},"column":{"s":21,"e":26}},"dim":["","list.20","listItem.0","paragraph.0","text.2"],"code":" and "},{"id":"/root/children/20/children/0/children/0/children/3","type":"inlineCode","loc":{"start":839,"end":847,"line":{"s":52,"e":52,"code":["- will add `**/*.mdd` and `*.json` to list"]},"column":{"s":26,"e":34}},"dim":["","list.20","listItem.0","paragraph.0","inlineCode.3"],"code":"`*.json`"},{"id":"/root/children/20/children/0/children/0/children/4","type":"text","loc":{"start":847,"end":855,"line":{"s":52,"e":52,"code":["- will add `**/*.mdd` and `*.json` to list"]},"column":{"s":34,"e":42}},"dim":["","list.20","listItem.0","paragraph.0","text.4"],"code":" to list"},{"id":"/root/children/20/children/1","type":"listItem","loc":{"start":857,"end":871,"line":{"s":54,"e":54,"code":["- or manually:"]},"column":{"s":0,"e":14}},"dim":["","list.20","listItem.1"],"code":"- or manually:"},{"id":"/root/children/20/children/1/children/0","type":"paragraph","loc":{"start":859,"end":871,"line":{"s":54,"e":54,"code":["- or manually:"]},"column":{"s":2,"e":14}},"dim":["","list.20","listItem.1","paragraph.0"],"code":"or manually:"},{"id":"/root/children/20/children/1/children/0/children/0","type":"text","loc":{"start":859,"end":871,"line":{"s":54,"e":54,"code":["- or manually:"]},"column":{"s":2,"e":14}},"dim":["","list.20","listItem.1","paragraph.0","text.0"],"code":"or manually:"},{"id":"/root/children/20/children/2","type":"listItem","loc":{"start":872,"end":902,"line":{"s":55,"e":55,"code":["- you may edit $dbPopulateList"]},"column":{"s":0,"e":30}},"dim":["","list.20","listItem.2"],"code":"- you may edit $dbPopulateList"},{"id":"/root/children/20/children/2/children/0","type":"paragraph","loc":{"start":874,"end":902,"line":{"s":55,"e":55,"code":["- you may edit $dbPopulateList"]},"column":{"s":2,"e":30}},"dim":["","list.20","listItem.2","paragraph.0"],"code":"you may edit $dbPopulateList"},{"id":"/root/children/20/children/2/children/0/children/0","type":"text","loc":{"start":874,"end":902,"line":{"s":55,"e":55,"code":["- you may edit $dbPopulateList"]},"column":{"s":2,"e":30}},"dim":["","list.20","listItem.2","paragraph.0","text.0"],"code":"you may edit $dbPopulateList"},{"id":"/root/children/21","type":"code","loc":{"start":904,"end":1226,"line":{"s":57,"e":68,"code":["```bash","# --base-directory $dir","","fd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList","","fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList","","# or only add arbitrary files:","","rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList","","```"]},"column":{"s":0,"e":3}},"dim":["","code.21"],"code":"```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```","symbName":"code","symbRange":[1228,2297],"symbRangeL":[null,129],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>"},{"id":"/root/children/22","type":"list","loc":{"start":1228,"end":1258,"line":{"s":70,"e":70,"code":["- see also `ast-lake populate`"]},"column":{"s":0,"e":30}},"dim":["","list.22"],"code":"- see also `ast-lake populate`","symbName":"list","symbRange":[1260,1272],"symbRangeL":[70,73],"outerCode":"\n### populate","outerHtml":"\n<h3>populate</h3>"},{"id":"/root/children/22/children/0","type":"listItem","loc":{"start":1228,"end":1258,"line":{"s":70,"e":70,"code":["- see also `ast-lake populate`"]},"column":{"s":0,"e":30}},"dim":["","list.22","listItem.0"],"code":"- see also `ast-lake populate`"},{"id":"/root/children/22/children/0/children/0","type":"paragraph","loc":{"start":1230,"end":1258,"line":{"s":70,"e":70,"code":["- see also `ast-lake populate`"]},"column":{"s":2,"e":30}},"dim":["","list.22","listItem.0","paragraph.0"],"code":"see also `ast-lake populate`"},{"id":"/root/children/22/children/0/children/0/children/0","type":"text","loc":{"start":1230,"end":1239,"line":{"s":70,"e":70,"code":["- see also `ast-lake populate`"]},"column":{"s":2,"e":11}},"dim":["","list.22","listItem.0","paragraph.0","text.0"],"code":"see also "},{"id":"/root/children/22/children/0/children/0/children/1","type":"inlineCode","loc":{"start":1239,"end":1258,"line":{"s":70,"e":70,"code":["- see also `ast-lake populate`"]},"column":{"s":11,"e":30}},"dim":["","list.22","listItem.0","paragraph.0","inlineCode.1"],"code":"`ast-lake populate`"},{"id":"/root/children/23","type":"heading","loc":{"start":1260,"end":1272,"line":{"s":72,"e":72,"code":["### populate"]},"column":{"s":0,"e":12}},"dim":["","heading.23"],"code":"### populate","symbName":"heading","symbRange":[1274,1326],"symbRangeL":[72,75],"outerCode":"\n- ensure all ast's (from on the list) are up to date","outerHtml":"\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>"},{"id":"/root/children/23/children/0","type":"text","loc":{"start":1264,"end":1272,"line":{"s":72,"e":72,"code":["### populate"]},"column":{"s":4,"e":12}},"dim":["","heading.23","text.0"],"code":"populate"},{"id":"/root/children/24","type":"list","loc":{"start":1274,"end":1326,"line":{"s":74,"e":74,"code":["- ensure all ast's (from on the list) are up to date"]},"column":{"s":0,"e":52}},"dim":["","list.24"],"code":"- ensure all ast's (from on the list) are up to date","symbName":"list","symbRange":[1328,1342],"symbRangeL":[74,77],"outerCode":"\n### query TERM","outerHtml":"\n<h3>query TERM</h3>"},{"id":"/root/children/24/children/0","type":"listItem","loc":{"start":1274,"end":1326,"line":{"s":74,"e":74,"code":["- ensure all ast's (from on the list) are up to date"]},"column":{"s":0,"e":52}},"dim":["","list.24","listItem.0"],"code":"- ensure all ast's (from on the list) are up to date"},{"id":"/root/children/24/children/0/children/0","type":"paragraph","loc":{"start":1276,"end":1326,"line":{"s":74,"e":74,"code":["- ensure all ast's (from on the list) are up to date"]},"column":{"s":2,"e":52}},"dim":["","list.24","listItem.0","paragraph.0"],"code":"ensure all ast's (from on the list) are up to date"},{"id":"/root/children/24/children/0/children/0/children/0","type":"text","loc":{"start":1276,"end":1326,"line":{"s":74,"e":74,"code":["- ensure all ast's (from on the list) are up to date"]},"column":{"s":2,"e":52}},"dim":["","list.24","listItem.0","paragraph.0","text.0"],"code":"ensure all ast's (from on the list) are up to date"},{"id":"/root/children/25","type":"heading","loc":{"start":1328,"end":1342,"line":{"s":76,"e":76,"code":["### query TERM"]},"column":{"s":0,"e":14}},"dim":["","heading.25"],"code":"### query TERM","symbName":"heading","symbRange":[1344,1409],"symbRangeL":[76,81],"outerCode":"\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$","outerHtml":"\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>"},{"id":"/root/children/25/children/0","type":"text","loc":{"start":1332,"end":1342,"line":{"s":76,"e":76,"code":["### query TERM"]},"column":{"s":4,"e":14}},"dim":["","heading.25","text.0"],"code":"query TERM"},{"id":"/root/children/26","type":"list","loc":{"start":1344,"end":1409,"line":{"s":78,"e":80,"code":["- ast-lake query term","- TODO term patterns ?","- TODO regex: ^cs.+$"]},"column":{"s":0,"e":20}},"dim":["","list.26"],"code":"- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$","symbName":"list","symbRange":[1411,1428],"symbRangeL":[78,83],"outerCode":"- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM","outerHtml":"<ul><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>"},{"id":"/root/children/26/children/0","type":"listItem","loc":{"start":1344,"end":1365,"line":{"s":78,"e":78,"code":["- ast-lake query term"]},"column":{"s":0,"e":21}},"dim":["","list.26","listItem.0"],"code":"- ast-lake query term"},{"id":"/root/children/26/children/0/children/0","type":"paragraph","loc":{"start":1346,"end":1365,"line":{"s":78,"e":78,"code":["- ast-lake query term"]},"column":{"s":2,"e":21}},"dim":["","list.26","listItem.0","paragraph.0"],"code":"ast-lake query term"},{"id":"/root/children/26/children/0/children/0/children/0","type":"text","loc":{"start":1346,"end":1365,"line":{"s":78,"e":78,"code":["- ast-lake query term"]},"column":{"s":2,"e":21}},"dim":["","list.26","listItem.0","paragraph.0","text.0"],"code":"ast-lake query term"},{"id":"/root/children/26/children/1","type":"listItem","loc":{"start":1366,"end":1388,"line":{"s":79,"e":79,"code":["- TODO term patterns ?"]},"column":{"s":0,"e":22}},"dim":["","list.26","listItem.1"],"code":"- TODO term patterns ?"},{"id":"/root/children/26/children/1/children/0","type":"paragraph","loc":{"start":1368,"end":1388,"line":{"s":79,"e":79,"code":["- TODO term patterns ?"]},"column":{"s":2,"e":22}},"dim":["","list.26","listItem.1","paragraph.0"],"code":"TODO term patterns ?"},{"id":"/root/children/26/children/1/children/0/children/0","type":"text","loc":{"start":1368,"end":1388,"line":{"s":79,"e":79,"code":["- TODO term patterns ?"]},"column":{"s":2,"e":22}},"dim":["","list.26","listItem.1","paragraph.0","text.0"],"code":"TODO term patterns ?"},{"id":"/root/children/26/children/2","type":"listItem","loc":{"start":1389,"end":1409,"line":{"s":80,"e":80,"code":["- TODO regex: ^cs.+$"]},"column":{"s":0,"e":20}},"dim":["","list.26","listItem.2"],"code":"- TODO regex: ^cs.+$"},{"id":"/root/children/26/children/2/children/0","type":"paragraph","loc":{"start":1391,"end":1409,"line":{"s":80,"e":80,"code":["- TODO regex: ^cs.+$"]},"column":{"s":2,"e":20}},"dim":["","list.26","listItem.2","paragraph.0"],"code":"TODO regex: ^cs.+$"},{"id":"/root/children/26/children/2/children/0/children/0","type":"text","loc":{"start":1391,"end":1409,"line":{"s":80,"e":80,"code":["- TODO regex: ^cs.+$"]},"column":{"s":2,"e":20}},"dim":["","list.26","listItem.2","paragraph.0","text.0"],"code":"TODO regex: ^cs.+$"},{"id":"/root/children/27","type":"heading","loc":{"start":1411,"end":1428,"line":{"s":82,"e":82,"code":["### queryraw TERM"]},"column":{"s":0,"e":17}},"dim":["","heading.27"],"code":"### queryraw TERM","symbName":"heading","symbRange":[1430,1467],"symbRangeL":[82,85],"outerCode":"\n- same as query, but returns raw JSON","outerHtml":"\n<ul><li>same as query, but returns raw JSON</li></ul>"},{"id":"/root/children/27/children/0","type":"text","loc":{"start":1415,"end":1428,"line":{"s":82,"e":82,"code":["### queryraw TERM"]},"column":{"s":4,"e":17}},"dim":["","heading.27","text.0"],"code":"queryraw TERM"},{"id":"/root/children/28","type":"list","loc":{"start":1430,"end":1467,"line":{"s":84,"e":84,"code":["- same as query, but returns raw JSON"]},"column":{"s":0,"e":37}},"dim":["","list.28"],"code":"- same as query, but returns raw JSON","symbName":"list","symbRange":[1469,1478],"symbRangeL":[84,87],"outerCode":"\n### flast","outerHtml":"\n<h3>flast</h3>"},{"id":"/root/children/28/children/0","type":"listItem","loc":{"start":1430,"end":1467,"line":{"s":84,"e":84,"code":["- same as query, but returns raw JSON"]},"column":{"s":0,"e":37}},"dim":["","list.28","listItem.0"],"code":"- same as query, but returns raw JSON"},{"id":"/root/children/28/children/0/children/0","type":"paragraph","loc":{"start":1432,"end":1467,"line":{"s":84,"e":84,"code":["- same as query, but returns raw JSON"]},"column":{"s":2,"e":37}},"dim":["","list.28","listItem.0","paragraph.0"],"code":"same as query, but returns raw JSON"},{"id":"/root/children/28/children/0/children/0/children/0","type":"text","loc":{"start":1432,"end":1467,"line":{"s":84,"e":84,"code":["- same as query, but returns raw JSON"]},"column":{"s":2,"e":37}},"dim":["","list.28","listItem.0","paragraph.0","text.0"],"code":"same as query, but returns raw JSON"},{"id":"/root/children/29","type":"heading","loc":{"start":1469,"end":1478,"line":{"s":86,"e":86,"code":["### flast"]},"column":{"s":0,"e":9}},"dim":["","heading.29"],"code":"### flast","symbName":"heading","symbRange":[1480,1521],"symbRangeL":[86,90],"outerCode":"\n- basically a dump\n- print all raw flASTs","outerHtml":"\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>"},{"id":"/root/children/29/children/0","type":"text","loc":{"start":1473,"end":1478,"line":{"s":86,"e":86,"code":["### flast"]},"column":{"s":4,"e":9}},"dim":["","heading.29","text.0"],"code":"flast"},{"id":"/root/children/30","type":"list","loc":{"start":1480,"end":1521,"line":{"s":88,"e":89,"code":["- basically a dump","- print all raw flASTs"]},"column":{"s":0,"e":22}},"dim":["","list.30"],"code":"- basically a dump\n- print all raw flASTs","symbName":"list","symbRange":[1523,1532],"symbRangeL":[88,92],"outerCode":"- print all raw flASTs\n\n### nodes","outerHtml":"<ul><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>"},{"id":"/root/children/30/children/0","type":"listItem","loc":{"start":1480,"end":1498,"line":{"s":88,"e":88,"code":["- basically a dump"]},"column":{"s":0,"e":18}},"dim":["","list.30","listItem.0"],"code":"- basically a dump"},{"id":"/root/children/30/children/0/children/0","type":"paragraph","loc":{"start":1482,"end":1498,"line":{"s":88,"e":88,"code":["- basically a dump"]},"column":{"s":2,"e":18}},"dim":["","list.30","listItem.0","paragraph.0"],"code":"basically a dump"},{"id":"/root/children/30/children/0/children/0/children/0","type":"text","loc":{"start":1482,"end":1498,"line":{"s":88,"e":88,"code":["- basically a dump"]},"column":{"s":2,"e":18}},"dim":["","list.30","listItem.0","paragraph.0","text.0"],"code":"basically a dump"},{"id":"/root/children/30/children/1","type":"listItem","loc":{"start":1499,"end":1521,"line":{"s":89,"e":89,"code":["- print all raw flASTs"]},"column":{"s":0,"e":22}},"dim":["","list.30","listItem.1"],"code":"- print all raw flASTs"},{"id":"/root/children/30/children/1/children/0","type":"paragraph","loc":{"start":1501,"end":1521,"line":{"s":89,"e":89,"code":["- print all raw flASTs"]},"column":{"s":2,"e":22}},"dim":["","list.30","listItem.1","paragraph.0"],"code":"print all raw flASTs"},{"id":"/root/children/30/children/1/children/0/children/0","type":"text","loc":{"start":1501,"end":1521,"line":{"s":89,"e":89,"code":["- print all raw flASTs"]},"column":{"s":2,"e":22}},"dim":["","list.30","listItem.1","paragraph.0","text.0"],"code":"print all raw flASTs"},{"id":"/root/children/31","type":"heading","loc":{"start":1523,"end":1532,"line":{"s":91,"e":91,"code":["### nodes"]},"column":{"s":0,"e":9}},"dim":["","heading.31"],"code":"### nodes","symbName":"heading","symbRange":[1534,1551],"symbRangeL":[91,94],"outerCode":"\n- print raw nodes","outerHtml":"\n<ul><li>print raw nodes</li></ul>"},{"id":"/root/children/31/children/0","type":"text","loc":{"start":1527,"end":1532,"line":{"s":91,"e":91,"code":["### nodes"]},"column":{"s":4,"e":9}},"dim":["","heading.31","text.0"],"code":"nodes"},{"id":"/root/children/32","type":"list","loc":{"start":1534,"end":1551,"line":{"s":93,"e":93,"code":["- print raw nodes"]},"column":{"s":0,"e":17}},"dim":["","list.32"],"code":"- print raw nodes","symbName":"list","symbRange":[1553,1560],"symbRangeL":[93,96],"outerCode":"\n### mdg","outerHtml":"\n<h3>mdg</h3>"},{"id":"/root/children/32/children/0","type":"listItem","loc":{"start":1534,"end":1551,"line":{"s":93,"e":93,"code":["- print raw nodes"]},"column":{"s":0,"e":17}},"dim":["","list.32","listItem.0"],"code":"- print raw nodes"},{"id":"/root/children/32/children/0/children/0","type":"paragraph","loc":{"start":1536,"end":1551,"line":{"s":93,"e":93,"code":["- print raw nodes"]},"column":{"s":2,"e":17}},"dim":["","list.32","listItem.0","paragraph.0"],"code":"print raw nodes"},{"id":"/root/children/32/children/0/children/0/children/0","type":"text","loc":{"start":1536,"end":1551,"line":{"s":93,"e":93,"code":["- print raw nodes"]},"column":{"s":2,"e":17}},"dim":["","list.32","listItem.0","paragraph.0","text.0"],"code":"print raw nodes"},{"id":"/root/children/33","type":"heading","loc":{"start":1553,"end":1560,"line":{"s":95,"e":95,"code":["### mdg"]},"column":{"s":0,"e":7}},"dim":["","heading.33"],"code":"### mdg","symbName":"heading","symbRange":[1562,1616],"symbRangeL":[95,99],"outerCode":"\n- TODO really?\n- make one big mdg graph from all mdd's","outerHtml":"\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>"},{"id":"/root/children/33/children/0","type":"text","loc":{"start":1557,"end":1560,"line":{"s":95,"e":95,"code":["### mdg"]},"column":{"s":4,"e":7}},"dim":["","heading.33","text.0"],"code":"mdg"},{"id":"/root/children/34","type":"list","loc":{"start":1562,"end":1616,"line":{"s":97,"e":98,"code":["- TODO really?","- make one big mdg graph from all mdd's"]},"column":{"s":0,"e":39}},"dim":["","list.34"],"code":"- TODO really?\n- make one big mdg graph from all mdd's","symbName":"list","symbRange":[1618,1628],"symbRangeL":[97,101],"outerCode":"- make one big mdg graph from all mdd's\n\n### mosaic","outerHtml":"<ul><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>"},{"id":"/root/children/34/children/0","type":"listItem","loc":{"start":1562,"end":1576,"line":{"s":97,"e":97,"code":["- TODO really?"]},"column":{"s":0,"e":14}},"dim":["","list.34","listItem.0"],"code":"- TODO really?"},{"id":"/root/children/34/children/0/children/0","type":"paragraph","loc":{"start":1564,"end":1576,"line":{"s":97,"e":97,"code":["- TODO really?"]},"column":{"s":2,"e":14}},"dim":["","list.34","listItem.0","paragraph.0"],"code":"TODO really?"},{"id":"/root/children/34/children/0/children/0/children/0","type":"text","loc":{"start":1564,"end":1576,"line":{"s":97,"e":97,"code":["- TODO really?"]},"column":{"s":2,"e":14}},"dim":["","list.34","listItem.0","paragraph.0","text.0"],"code":"TODO really?"},{"id":"/root/children/34/children/1","type":"listItem","loc":{"start":1577,"end":1616,"line":{"s":98,"e":98,"code":["- make one big mdg graph from all mdd's"]},"column":{"s":0,"e":39}},"dim":["","list.34","listItem.1"],"code":"- make one big mdg graph from all mdd's"},{"id":"/root/children/34/children/1/children/0","type":"paragraph","loc":{"start":1579,"end":1616,"line":{"s":98,"e":98,"code":["- make one big mdg graph from all mdd's"]},"column":{"s":2,"e":39}},"dim":["","list.34","listItem.1","paragraph.0"],"code":"make one big mdg graph from all mdd's"},{"id":"/root/children/34/children/1/children/0/children/0","type":"text","loc":{"start":1579,"end":1616,"line":{"s":98,"e":98,"code":["- make one big mdg graph from all mdd's"]},"column":{"s":2,"e":39}},"dim":["","list.34","listItem.1","paragraph.0","text.0"],"code":"make one big mdg graph from all mdd's"},{"id":"/root/children/35","type":"heading","loc":{"start":1618,"end":1628,"line":{"s":100,"e":100,"code":["### mosaic"]},"column":{"s":0,"e":10}},"dim":["","heading.35"],"code":"### mosaic","symbName":"heading","symbRange":[1630,1653],"symbRangeL":[100,104],"outerCode":"\n- mosaic\n- TODO really?","outerHtml":"\n<ul><li>mosaic</li><li>TODO really?</li></ul>"},{"id":"/root/children/35/children/0","type":"text","loc":{"start":1622,"end":1628,"line":{"s":100,"e":100,"code":["### mosaic"]},"column":{"s":4,"e":10}},"dim":["","heading.35","text.0"],"code":"mosaic"},{"id":"/root/children/36","type":"list","loc":{"start":1630,"end":1653,"line":{"s":102,"e":103,"code":["- mosaic","- TODO really?"]},"column":{"s":0,"e":14}},"dim":["","list.36"],"code":"- mosaic\n- TODO really?","symbName":"list","symbRange":[1655,1665],"symbRangeL":[102,106],"outerCode":"- TODO really?\n\n### mprocs","outerHtml":"<ul><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>"},{"id":"/root/children/36/children/0","type":"listItem","loc":{"start":1630,"end":1638,"line":{"s":102,"e":102,"code":["- mosaic"]},"column":{"s":0,"e":8}},"dim":["","list.36","listItem.0"],"code":"- mosaic"},{"id":"/root/children/36/children/0/children/0","type":"paragraph","loc":{"start":1632,"end":1638,"line":{"s":102,"e":102,"code":["- mosaic"]},"column":{"s":2,"e":8}},"dim":["","list.36","listItem.0","paragraph.0"],"code":"mosaic"},{"id":"/root/children/36/children/0/children/0/children/0","type":"text","loc":{"start":1632,"end":1638,"line":{"s":102,"e":102,"code":["- mosaic"]},"column":{"s":2,"e":8}},"dim":["","list.36","listItem.0","paragraph.0","text.0"],"code":"mosaic"},{"id":"/root/children/36/children/1","type":"listItem","loc":{"start":1639,"end":1653,"line":{"s":103,"e":103,"code":["- TODO really?"]},"column":{"s":0,"e":14}},"dim":["","list.36","listItem.1"],"code":"- TODO really?"},{"id":"/root/children/36/children/1/children/0","type":"paragraph","loc":{"start":1641,"end":1653,"line":{"s":103,"e":103,"code":["- TODO really?"]},"column":{"s":2,"e":14}},"dim":["","list.36","listItem.1","paragraph.0"],"code":"TODO really?"},{"id":"/root/children/36/children/1/children/0/children/0","type":"text","loc":{"start":1641,"end":1653,"line":{"s":103,"e":103,"code":["- TODO really?"]},"column":{"s":2,"e":14}},"dim":["","list.36","listItem.1","paragraph.0","text.0"],"code":"TODO really?"},{"id":"/root/children/37","type":"heading","loc":{"start":1655,"end":1665,"line":{"s":105,"e":105,"code":["### mprocs"]},"column":{"s":0,"e":10}},"dim":["","heading.37"],"code":"### mprocs","symbName":"heading","symbRange":[1667,1688],"symbRangeL":[105,108],"outerCode":"\n- TODO support mprocs","outerHtml":"\n<ul><li>TODO support mprocs</li></ul>"},{"id":"/root/children/37/children/0","type":"text","loc":{"start":1659,"end":1665,"line":{"s":105,"e":105,"code":["### mprocs"]},"column":{"s":4,"e":10}},"dim":["","heading.37","text.0"],"code":"mprocs"},{"id":"/root/children/38","type":"list","loc":{"start":1667,"end":1688,"line":{"s":107,"e":107,"code":["- TODO support mprocs"]},"column":{"s":0,"e":21}},"dim":["","list.38"],"code":"- TODO support mprocs","symbName":"list","symbRange":[1690,1723],"symbRangeL":[107,112],"outerCode":"\n## recent updates\n\n### 2025-12-01","outerHtml":"\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>"},{"id":"/root/children/38/children/0","type":"listItem","loc":{"start":1667,"end":1688,"line":{"s":107,"e":107,"code":["- TODO support mprocs"]},"column":{"s":0,"e":21}},"dim":["","list.38","listItem.0"],"code":"- TODO support mprocs"},{"id":"/root/children/38/children/0/children/0","type":"paragraph","loc":{"start":1669,"end":1688,"line":{"s":107,"e":107,"code":["- TODO support mprocs"]},"column":{"s":2,"e":21}},"dim":["","list.38","listItem.0","paragraph.0"],"code":"TODO support mprocs"},{"id":"/root/children/38/children/0/children/0/children/0","type":"text","loc":{"start":1669,"end":1688,"line":{"s":107,"e":107,"code":["- TODO support mprocs"]},"column":{"s":2,"e":21}},"dim":["","list.38","listItem.0","paragraph.0","text.0"],"code":"TODO support mprocs"},{"id":"/root/children/39","type":"heading","loc":{"start":1690,"end":1707,"line":{"s":109,"e":109,"code":["## recent updates"]},"column":{"s":0,"e":17}},"dim":["","heading.39"],"code":"## recent updates","symbName":"heading","symbRange":[1709,16701],"symbRangeL":[109,110],"outerCode":"","outerHtml":""},{"id":"/root/children/39/children/0","type":"text","loc":{"start":1693,"end":1707,"line":{"s":109,"e":109,"code":["## recent updates"]},"column":{"s":3,"e":17}},"dim":["","heading.39","text.0"],"code":"recent updates"},{"id":"/root/children/40","type":"heading","loc":{"start":1709,"end":1723,"line":{"s":111,"e":111,"code":["### 2025-12-01"]},"column":{"s":0,"e":14}},"dim":["","heading.40"],"code":"### 2025-12-01","symbName":"heading","symbRange":[1725,1833],"symbRangeL":[111,116],"outerCode":"\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing","outerHtml":"\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>"},{"id":"/root/children/40/children/0","type":"text","loc":{"start":1713,"end":1723,"line":{"s":111,"e":111,"code":["### 2025-12-01"]},"column":{"s":4,"e":14}},"dim":["","heading.40","text.0"],"code":"2025-12-01"},{"id":"/root/children/41","type":"list","loc":{"start":1725,"end":1833,"line":{"s":113,"e":115,"code":["- actualizeNodes: chunked parallel processing","- chunk size: CPU count / 2","- faster database query processing"]},"column":{"s":0,"e":34}},"dim":["","list.41"],"code":"- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing","symbName":"list","symbRange":[1835,2011],"symbRangeL":[113,122],"outerCode":"- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**","outerHtml":"<ul><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>"},{"id":"/root/children/41/children/0","type":"listItem","loc":{"start":1725,"end":1770,"line":{"s":113,"e":113,"code":["- actualizeNodes: chunked parallel processing"]},"column":{"s":0,"e":45}},"dim":["","list.41","listItem.0"],"code":"- actualizeNodes: chunked parallel processing"},{"id":"/root/children/41/children/0/children/0","type":"paragraph","loc":{"start":1727,"end":1770,"line":{"s":113,"e":113,"code":["- actualizeNodes: chunked parallel processing"]},"column":{"s":2,"e":45}},"dim":["","list.41","listItem.0","paragraph.0"],"code":"actualizeNodes: chunked parallel processing"},{"id":"/root/children/41/children/0/children/0/children/0","type":"text","loc":{"start":1727,"end":1770,"line":{"s":113,"e":113,"code":["- actualizeNodes: chunked parallel processing"]},"column":{"s":2,"e":45}},"dim":["","list.41","listItem.0","paragraph.0","text.0"],"code":"actualizeNodes: chunked parallel processing"},{"id":"/root/children/41/children/1","type":"listItem","loc":{"start":1771,"end":1798,"line":{"s":114,"e":114,"code":["- chunk size: CPU count / 2"]},"column":{"s":0,"e":27}},"dim":["","list.41","listItem.1"],"code":"- chunk size: CPU count / 2"},{"id":"/root/children/41/children/1/children/0","type":"paragraph","loc":{"start":1773,"end":1798,"line":{"s":114,"e":114,"code":["- chunk size: CPU count / 2"]},"column":{"s":2,"e":27}},"dim":["","list.41","listItem.1","paragraph.0"],"code":"chunk size: CPU count / 2"},{"id":"/root/children/41/children/1/children/0/children/0","type":"text","loc":{"start":1773,"end":1798,"line":{"s":114,"e":114,"code":["- chunk size: CPU count / 2"]},"column":{"s":2,"e":27}},"dim":["","list.41","listItem.1","paragraph.0","text.0"],"code":"chunk size: CPU count / 2"},{"id":"/root/children/41/children/2","type":"listItem","loc":{"start":1799,"end":1833,"line":{"s":115,"e":115,"code":["- faster database query processing"]},"column":{"s":0,"e":34}},"dim":["","list.41","listItem.2"],"code":"- faster database query processing"},{"id":"/root/children/41/children/2/children/0","type":"paragraph","loc":{"start":1801,"end":1833,"line":{"s":115,"e":115,"code":["- faster database query processing"]},"column":{"s":2,"e":34}},"dim":["","list.41","listItem.2","paragraph.0"],"code":"faster database query processing"},{"id":"/root/children/41/children/2/children/0/children/0","type":"text","loc":{"start":1801,"end":1833,"line":{"s":115,"e":115,"code":["- faster database query processing"]},"column":{"s":2,"e":34}},"dim":["","list.41","listItem.2","paragraph.0","text.0"],"code":"faster database query processing"},{"id":"/root/children/42","type":"heading","loc":{"start":1835,"end":1846,"line":{"s":117,"e":117,"code":["# db tables"]},"column":{"s":0,"e":11}},"dim":["","heading.42"],"code":"# db tables","symbName":"heading","symbRange":[1848,2263],"symbRangeL":[117,127],"outerCode":"\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.","outerHtml":"\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>"},{"id":"/root/children/42/children/0","type":"text","loc":{"start":1837,"end":1846,"line":{"s":117,"e":117,"code":["# db tables"]},"column":{"s":2,"e":11}},"dim":["","heading.42","text.0"],"code":"db tables"},{"id":"/root/children/43","type":"paragraph","loc":{"start":1848,"end":1970,"line":{"s":119,"e":119,"code":["These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes."]},"column":{"s":0,"e":122}},"dim":["","paragraph.43"],"code":"These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes."},{"id":"/root/children/43/children/0","type":"text","loc":{"start":1848,"end":1970,"line":{"s":119,"e":119,"code":["These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes."]},"column":{"s":0,"e":122}},"dim":["","paragraph.43","text.0"],"code":"These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes."},{"id":"/root/children/44","type":"paragraph","loc":{"start":1972,"end":2011,"line":{"s":121,"e":121,"code":["**Two parallel implementations exist:**"]},"column":{"s":0,"e":39}},"dim":["","paragraph.44"],"code":"**Two parallel implementations exist:**"},{"id":"/root/children/44/children/0","type":"strong","loc":{"start":1972,"end":2011,"line":{"s":121,"e":121,"code":["**Two parallel implementations exist:**"]},"column":{"s":0,"e":39}},"dim":["","paragraph.44","strong.0"],"code":"**Two parallel implementations exist:**"},{"id":"/root/children/44/children/0/children/0","type":"text","loc":{"start":1974,"end":2009,"line":{"s":121,"e":121,"code":["**Two parallel implementations exist:**"]},"column":{"s":2,"e":37}},"dim":["","paragraph.44","strong.0","text.0"],"code":"Two parallel implementations exist:"},{"id":"/root/children/45","type":"list","loc":{"start":2013,"end":2190,"line":{"s":123,"e":124,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files","- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":0,"e":92}},"dim":["","list.45"],"code":"- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`","symbName":"list","symbRange":[2192,8506],"symbRangeL":[123,241],"outerCode":"- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):","outerHtml":"<ul><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>"},{"id":"/root/children/45/children/0","type":"listItem","loc":{"start":2013,"end":2097,"line":{"s":123,"e":123,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"]},"column":{"s":0,"e":84}},"dim":["","list.45","listItem.0"],"code":"- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"},{"id":"/root/children/45/children/0/children/0","type":"paragraph","loc":{"start":2015,"end":2097,"line":{"s":123,"e":123,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"]},"column":{"s":2,"e":84}},"dim":["","list.45","listItem.0","paragraph.0"],"code":"**Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"},{"id":"/root/children/45/children/0/children/0/children/0","type":"strong","loc":{"start":2015,"end":2030,"line":{"s":123,"e":123,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"]},"column":{"s":2,"e":17}},"dim":["","list.45","listItem.0","paragraph.0","strong.0"],"code":"**Shell-based**"},{"id":"/root/children/45/children/0/children/0/children/0/children/0","type":"text","loc":{"start":2017,"end":2028,"line":{"s":123,"e":123,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"]},"column":{"s":4,"e":15}},"dim":["","list.45","listItem.0","paragraph.0","strong.0","text.0"],"code":"Shell-based"},{"id":"/root/children/45/children/0/children/0/children/1","type":"text","loc":{"start":2030,"end":2032,"line":{"s":123,"e":123,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"]},"column":{"s":17,"e":19}},"dim":["","list.45","listItem.0","paragraph.0","text.1"],"code":" ("},{"id":"/root/children/45/children/0/children/0/children/2","type":"inlineCode","loc":{"start":2032,"end":2045,"line":{"s":123,"e":123,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"]},"column":{"s":19,"e":32}},"dim":["","list.45","listItem.0","paragraph.0","inlineCode.2"],"code":"`ast-lake.sh`"},{"id":"/root/children/45/children/0/children/0/children/3","type":"text","loc":{"start":2045,"end":2097,"line":{"s":123,"e":123,"code":["- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files"]},"column":{"s":32,"e":84}},"dim":["","list.45","listItem.0","paragraph.0","text.3"],"code":") — older approach using local SQLite + nljson files"},{"id":"/root/children/45/children/1","type":"listItem","loc":{"start":2098,"end":2190,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":0,"e":92}},"dim":["","list.45","listItem.1"],"code":"- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"},{"id":"/root/children/45/children/1/children/0","type":"paragraph","loc":{"start":2100,"end":2190,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":2,"e":92}},"dim":["","list.45","listItem.1","paragraph.0"],"code":"**Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"},{"id":"/root/children/45/children/1/children/0/children/0","type":"strong","loc":{"start":2100,"end":2117,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":2,"e":19}},"dim":["","list.45","listItem.1","paragraph.0","strong.0"],"code":"**Node.js-based**"},{"id":"/root/children/45/children/1/children/0/children/0/children/0","type":"text","loc":{"start":2102,"end":2115,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":4,"e":17}},"dim":["","list.45","listItem.1","paragraph.0","strong.0","text.0"],"code":"Node.js-based"},{"id":"/root/children/45/children/1/children/0/children/1","type":"text","loc":{"start":2117,"end":2119,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":19,"e":21}},"dim":["","list.45","listItem.1","paragraph.0","text.1"],"code":" ("},{"id":"/root/children/45/children/1/children/0/children/2","type":"inlineCode","loc":{"start":2119,"end":2133,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":21,"e":35}},"dim":["","list.45","listItem.1","paragraph.0","inlineCode.2"],"code":"`ast-lake.mjs`"},{"id":"/root/children/45/children/1/children/0/children/3","type":"text","loc":{"start":2133,"end":2176,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":35,"e":78}},"dim":["","list.45","listItem.1","paragraph.0","text.3"],"code":") — newer approach using remote SQLite via "},{"id":"/root/children/45/children/1/children/0/children/4","type":"inlineCode","loc":{"start":2176,"end":2190,"line":{"s":124,"e":124,"code":["- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`"]},"column":{"s":78,"e":92}},"dim":["","list.45","listItem.1","paragraph.0","inlineCode.4"],"code":"`RemoteSqlite`"},{"id":"/root/children/46","type":"paragraph","loc":{"start":2192,"end":2263,"line":{"s":126,"e":126,"code":["Both follow the same pipeline: extract flast → normalize → store nodes."]},"column":{"s":0,"e":71}},"dim":["","paragraph.46"],"code":"Both follow the same pipeline: extract flast → normalize → store nodes."},{"id":"/root/children/46/children/0","type":"text","loc":{"start":2192,"end":2263,"line":{"s":126,"e":126,"code":["Both follow the same pipeline: extract flast → normalize → store nodes."]},"column":{"s":0,"e":71}},"dim":["","paragraph.46","text.0"],"code":"Both follow the same pipeline: extract flast → normalize → store nodes."},{"id":"/root/children/47","type":"heading","loc":{"start":2265,"end":2297,"line":{"s":128,"e":128,"code":["## The pipeline (how data flows)"]},"column":{"s":0,"e":32}},"dim":["","heading.47"],"code":"## The pipeline (how data flows)","symbName":"heading","symbRange":[2299,3038],"symbRangeL":[128,148],"outerCode":"\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```","outerHtml":"\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>"},{"id":"/root/children/47/children/0","type":"text","loc":{"start":2268,"end":2297,"line":{"s":128,"e":128,"code":["## The pipeline (how data flows)"]},"column":{"s":3,"e":32}},"dim":["","heading.47","text.0"],"code":"The pipeline (how data flows)"},{"id":"/root/children/48","type":"code","loc":{"start":2299,"end":3038,"line":{"s":130,"e":147,"code":["```","File on disk","  │","  ▼","astHole() → astNavigator() — reads file, calls language-specific extractor","  │                            (extractFlastMarkdown, extractFlastJavascript,","  │                             extractFlastJson, extractFlastBash, ...)","  ▼","cache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array","  │","  ▼","actualizeNodes() → normalizeNodes() — extracts symbolic types from flast","  │                                   (heading, link, comment, FunctionDeclaration, Identifier)","  ▼","cache_ast_lake_nodes          — one row per AST node; id=fn:offset","cache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)","cache_ast_lake_nodes_backup   — backup of old nodes before update","```"]},"column":{"s":0,"e":3}},"dim":["","code.48"],"code":"```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```","symbName":"code","symbRange":[3040,5280],"symbRangeL":[null,181],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>"},{"id":"/root/children/49","type":"heading","loc":{"start":3040,"end":3148,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":0,"e":108}},"dim":["","heading.49"],"code":"## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`","symbName":"heading","symbRange":[3150,4107],"symbRangeL":[149,167],"outerCode":"\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---","outerHtml":"\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>"},{"id":"/root/children/49/children/0","type":"inlineCode","loc":{"start":3043,"end":3065,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":3,"e":25}},"dim":["","heading.49","inlineCode.0"],"code":"`cache_ast_lake_flast`"},{"id":"/root/children/49/children/1","type":"text","loc":{"start":3065,"end":3067,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":25,"e":27}},"dim":["","heading.49","text.1"],"code":", "},{"id":"/root/children/49/children/2","type":"inlineCode","loc":{"start":3067,"end":3089,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":27,"e":49}},"dim":["","heading.49","inlineCode.2"],"code":"`cache_ast_lake_nodes`"},{"id":"/root/children/49/children/3","type":"text","loc":{"start":3089,"end":3091,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":49,"e":51}},"dim":["","heading.49","text.3"],"code":", "},{"id":"/root/children/49/children/4","type":"inlineCode","loc":{"start":3091,"end":3117,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":51,"e":77}},"dim":["","heading.49","inlineCode.4"],"code":"`cache_ast_lake_nodes_ref`"},{"id":"/root/children/49/children/5","type":"text","loc":{"start":3117,"end":3119,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":77,"e":79}},"dim":["","heading.49","text.5"],"code":", "},{"id":"/root/children/49/children/6","type":"inlineCode","loc":{"start":3119,"end":3148,"line":{"s":149,"e":149,"code":["## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`"]},"column":{"s":79,"e":108}},"dim":["","heading.49","inlineCode.6"],"code":"`cache_ast_lake_nodes_backup`"},{"id":"/root/children/50","type":"paragraph","loc":{"start":3150,"end":3307,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":0,"e":157}},"dim":["","paragraph.50"],"code":"Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"},{"id":"/root/children/50/children/0","type":"text","loc":{"start":3150,"end":3162,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":0,"e":12}},"dim":["","paragraph.50","text.0"],"code":"Schema from "},{"id":"/root/children/50/children/1","type":"inlineCode","loc":{"start":3162,"end":3192,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":12,"e":42}},"dim":["","paragraph.50","inlineCode.1"],"code":"`sqlCreateTableCache(tblName)`"},{"id":"/root/children/50/children/2","type":"text","loc":{"start":3192,"end":3196,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":42,"e":46}},"dim":["","paragraph.50","text.2"],"code":" in "},{"id":"/root/children/50/children/3","type":"inlineCode","loc":{"start":3196,"end":3213,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":46,"e":63}},"dim":["","paragraph.50","inlineCode.3"],"code":"`ast-lake.mjs:51`"},{"id":"/root/children/50/children/4","type":"text","loc":{"start":3213,"end":3257,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":63,"e":107}},"dim":["","paragraph.50","text.4"],"code":". All four share the same column layout but "},{"id":"/root/children/50/children/5","type":"strong","loc":{"start":3257,"end":3306,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":107,"e":156}},"dim":["","paragraph.50","strong.5"],"code":"**each column has different semantics per table**"},{"id":"/root/children/50/children/5/children/0","type":"text","loc":{"start":3259,"end":3304,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":109,"e":154}},"dim":["","paragraph.50","strong.5","text.0"],"code":"each column has different semantics per table"},{"id":"/root/children/50/children/6","type":"text","loc":{"start":3306,"end":3307,"line":{"s":151,"e":151,"code":["Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:"]},"column":{"s":156,"e":157}},"dim":["","paragraph.50","text.6"],"code":":"},{"id":"/root/children/51","type":"paragraph","loc":{"start":3309,"end":4018,"line":{"s":153,"e":162,"code":["| Column  | Type          | Description                              |","| ------- | ------------- | ---------------------------------------- |","| `id`    | TEXT NOT NULL | Primary key                              |","| `value` | TEXT          | JSON payload of the node or flast        |","| `mt`    | TEXT          | Modification timestamp or mime-type      |","| `sem`   | TEXT          | Semantic type                            |","| `num1`  | INTEGER       | First numeric slot (position or line)    |","| `num2`  | INTEGER       | Second numeric slot (position or column) |","| `ref`   | TEXT          | Reference ID (trail or refId)            |","| `body`  | TEXT          | Node body text                           |"]},"column":{"s":0,"e":70}},"dim":["","paragraph.51"],"code":"| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |"},{"id":"/root/children/51/children/0","type":"text","loc":{"start":3309,"end":3453,"line":{"s":153,"e":155,"code":["| Column  | Type          | Description                              |","| ------- | ------------- | ---------------------------------------- |","| `id`    | TEXT NOT NULL | Primary key                              |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.51","text.0"],"code":"| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| "},{"id":"/root/children/51/children/1","type":"inlineCode","loc":{"start":3453,"end":3457,"line":{"s":155,"e":155,"code":["| `id`    | TEXT NOT NULL | Primary key                              |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.51","inlineCode.1"],"code":"`id`"},{"id":"/root/children/51/children/2","type":"text","loc":{"start":3457,"end":3524,"line":{"s":155,"e":156,"code":["| `id`    | TEXT NOT NULL | Primary key                              |","| `value` | TEXT          | JSON payload of the node or flast        |"]},"column":{"s":6,"e":2}},"dim":["","paragraph.51","text.2"],"code":"    | TEXT NOT NULL | Primary key                              |\n| "},{"id":"/root/children/51/children/3","type":"inlineCode","loc":{"start":3524,"end":3531,"line":{"s":156,"e":156,"code":["| `value` | TEXT          | JSON payload of the node or flast        |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.51","inlineCode.3"],"code":"`value`"},{"id":"/root/children/51/children/4","type":"text","loc":{"start":3531,"end":3595,"line":{"s":156,"e":157,"code":["| `value` | TEXT          | JSON payload of the node or flast        |","| `mt`    | TEXT          | Modification timestamp or mime-type      |"]},"column":{"s":9,"e":2}},"dim":["","paragraph.51","text.4"],"code":" | TEXT          | JSON payload of the node or flast        |\n| "},{"id":"/root/children/51/children/5","type":"inlineCode","loc":{"start":3595,"end":3599,"line":{"s":157,"e":157,"code":["| `mt`    | TEXT          | Modification timestamp or mime-type      |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.51","inlineCode.5"],"code":"`mt`"},{"id":"/root/children/51/children/6","type":"text","loc":{"start":3599,"end":3666,"line":{"s":157,"e":158,"code":["| `mt`    | TEXT          | Modification timestamp or mime-type      |","| `sem`   | TEXT          | Semantic type                            |"]},"column":{"s":6,"e":2}},"dim":["","paragraph.51","text.6"],"code":"    | TEXT          | Modification timestamp or mime-type      |\n| "},{"id":"/root/children/51/children/7","type":"inlineCode","loc":{"start":3666,"end":3671,"line":{"s":158,"e":158,"code":["| `sem`   | TEXT          | Semantic type                            |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.51","inlineCode.7"],"code":"`sem`"},{"id":"/root/children/51/children/8","type":"text","loc":{"start":3671,"end":3737,"line":{"s":158,"e":159,"code":["| `sem`   | TEXT          | Semantic type                            |","| `num1`  | INTEGER       | First numeric slot (position or line)    |"]},"column":{"s":7,"e":2}},"dim":["","paragraph.51","text.8"],"code":"   | TEXT          | Semantic type                            |\n| "},{"id":"/root/children/51/children/9","type":"inlineCode","loc":{"start":3737,"end":3743,"line":{"s":159,"e":159,"code":["| `num1`  | INTEGER       | First numeric slot (position or line)    |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.51","inlineCode.9"],"code":"`num1`"},{"id":"/root/children/51/children/10","type":"text","loc":{"start":3743,"end":3808,"line":{"s":159,"e":160,"code":["| `num1`  | INTEGER       | First numeric slot (position or line)    |","| `num2`  | INTEGER       | Second numeric slot (position or column) |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.51","text.10"],"code":"  | INTEGER       | First numeric slot (position or line)    |\n| "},{"id":"/root/children/51/children/11","type":"inlineCode","loc":{"start":3808,"end":3814,"line":{"s":160,"e":160,"code":["| `num2`  | INTEGER       | Second numeric slot (position or column) |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.51","inlineCode.11"],"code":"`num2`"},{"id":"/root/children/51/children/12","type":"text","loc":{"start":3814,"end":3879,"line":{"s":160,"e":161,"code":["| `num2`  | INTEGER       | Second numeric slot (position or column) |","| `ref`   | TEXT          | Reference ID (trail or refId)            |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.51","text.12"],"code":"  | INTEGER       | Second numeric slot (position or column) |\n| "},{"id":"/root/children/51/children/13","type":"inlineCode","loc":{"start":3879,"end":3884,"line":{"s":161,"e":161,"code":["| `ref`   | TEXT          | Reference ID (trail or refId)            |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.51","inlineCode.13"],"code":"`ref`"},{"id":"/root/children/51/children/14","type":"text","loc":{"start":3884,"end":3950,"line":{"s":161,"e":162,"code":["| `ref`   | TEXT          | Reference ID (trail or refId)            |","| `body`  | TEXT          | Node body text                           |"]},"column":{"s":7,"e":2}},"dim":["","paragraph.51","text.14"],"code":"   | TEXT          | Reference ID (trail or refId)            |\n| "},{"id":"/root/children/51/children/15","type":"inlineCode","loc":{"start":3950,"end":3956,"line":{"s":162,"e":162,"code":["| `body`  | TEXT          | Node body text                           |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.51","inlineCode.15"],"code":"`body`"},{"id":"/root/children/51/children/16","type":"text","loc":{"start":3956,"end":4018,"line":{"s":162,"e":162,"code":["| `body`  | TEXT          | Node body text                           |"]},"column":{"s":8,"e":70}},"dim":["","paragraph.51","text.16"],"code":"  | TEXT          | Node body text                           |"},{"id":"/root/children/52","type":"paragraph","loc":{"start":4020,"end":4102,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":0,"e":82}},"dim":["","paragraph.52"],"code":"**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."},{"id":"/root/children/52/children/0","type":"strong","loc":{"start":4020,"end":4039,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":0,"e":19}},"dim":["","paragraph.52","strong.0"],"code":"**Shared indexes:**"},{"id":"/root/children/52/children/0/children/0","type":"text","loc":{"start":4022,"end":4037,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":2,"e":17}},"dim":["","paragraph.52","strong.0","text.0"],"code":"Shared indexes:"},{"id":"/root/children/52/children/1","type":"text","loc":{"start":4039,"end":4040,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":19,"e":20}},"dim":["","paragraph.52","text.1"],"code":" "},{"id":"/root/children/52/children/2","type":"inlineCode","loc":{"start":4040,"end":4046,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":20,"e":26}},"dim":["","paragraph.52","inlineCode.2"],"code":"`num1`"},{"id":"/root/children/52/children/3","type":"text","loc":{"start":4046,"end":4048,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":26,"e":28}},"dim":["","paragraph.52","text.3"],"code":", "},{"id":"/root/children/52/children/4","type":"inlineCode","loc":{"start":4048,"end":4054,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":28,"e":34}},"dim":["","paragraph.52","inlineCode.4"],"code":"`num2`"},{"id":"/root/children/52/children/5","type":"text","loc":{"start":4054,"end":4056,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":34,"e":36}},"dim":["","paragraph.52","text.5"],"code":", "},{"id":"/root/children/52/children/6","type":"inlineCode","loc":{"start":4056,"end":4061,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":36,"e":41}},"dim":["","paragraph.52","inlineCode.6"],"code":"`sem`"},{"id":"/root/children/52/children/7","type":"text","loc":{"start":4061,"end":4063,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":41,"e":43}},"dim":["","paragraph.52","text.7"],"code":", "},{"id":"/root/children/52/children/8","type":"inlineCode","loc":{"start":4063,"end":4068,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":43,"e":48}},"dim":["","paragraph.52","inlineCode.8"],"code":"`ref`"},{"id":"/root/children/52/children/9","type":"text","loc":{"start":4068,"end":4081,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":48,"e":61}},"dim":["","paragraph.52","text.9"],"code":" (each named "},{"id":"/root/children/52/children/10","type":"inlineCode","loc":{"start":4081,"end":4100,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":61,"e":80}},"dim":["","paragraph.52","inlineCode.10"],"code":"`{table}_idx_{col}`"},{"id":"/root/children/52/children/11","type":"text","loc":{"start":4100,"end":4102,"line":{"s":164,"e":164,"code":["**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`)."]},"column":{"s":80,"e":82}},"dim":["","paragraph.52","text.11"],"code":")."},{"id":"/root/children/53","type":"thematicBreak","loc":{"start":4104,"end":4107,"line":{"s":166,"e":166,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.53"],"code":"---"},{"id":"/root/children/54","type":"heading","loc":{"start":4109,"end":4135,"line":{"s":168,"e":168,"code":["### `cache_ast_lake_flast`"]},"column":{"s":0,"e":26}},"dim":["","heading.54"],"code":"### `cache_ast_lake_flast`","symbName":"heading","symbRange":[4137,5772],"symbRangeL":[168,203],"outerCode":"\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---","outerHtml":"\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>"},{"id":"/root/children/54/children/0","type":"inlineCode","loc":{"start":4113,"end":4135,"line":{"s":168,"e":168,"code":["### `cache_ast_lake_flast`"]},"column":{"s":4,"e":26}},"dim":["","heading.54","inlineCode.0"],"code":"`cache_ast_lake_flast`"},{"id":"/root/children/55","type":"paragraph","loc":{"start":4137,"end":4259,"line":{"s":170,"e":170,"code":["**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor."]},"column":{"s":0,"e":122}},"dim":["","paragraph.55"],"code":"**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor."},{"id":"/root/children/55/children/0","type":"strong","loc":{"start":4137,"end":4146,"line":{"s":170,"e":170,"code":["**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor."]},"column":{"s":0,"e":9}},"dim":["","paragraph.55","strong.0"],"code":"**Role:**"},{"id":"/root/children/55/children/0/children/0","type":"text","loc":{"start":4139,"end":4144,"line":{"s":170,"e":170,"code":["**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor."]},"column":{"s":2,"e":7}},"dim":["","paragraph.55","strong.0","text.0"],"code":"Role:"},{"id":"/root/children/55/children/1","type":"text","loc":{"start":4146,"end":4259,"line":{"s":170,"e":170,"code":["**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor."]},"column":{"s":9,"e":122}},"dim":["","paragraph.55","text.1"],"code":" Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor."},{"id":"/root/children/56","type":"paragraph","loc":{"start":4261,"end":4970,"line":{"s":172,"e":176,"code":["| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |","| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |","| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |","| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |","| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |"]},"column":{"s":0,"e":141}},"dim":["","paragraph.56"],"code":"| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |"},{"id":"/root/children/56/children/0","type":"text","loc":{"start":4261,"end":4298,"line":{"s":172,"e":172,"code":["| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |"]},"column":{"s":0,"e":37}},"dim":["","paragraph.56","text.0"],"code":"| Column  | How it's populated (from "},{"id":"/root/children/56/children/1","type":"inlineCode","loc":{"start":4298,"end":4316,"line":{"s":172,"e":172,"code":["| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |"]},"column":{"s":37,"e":55}},"dim":["","paragraph.56","inlineCode.1"],"code":"`actualizeNodes()`"},{"id":"/root/children/56/children/2","type":"text","loc":{"start":4316,"end":4547,"line":{"s":172,"e":174,"code":["| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |","| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |","| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |"]},"column":{"s":55,"e":2}},"dim":["","paragraph.56","text.2"],"code":")   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| "},{"id":"/root/children/56/children/3","type":"inlineCode","loc":{"start":4547,"end":4551,"line":{"s":174,"e":174,"code":["| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.56","inlineCode.3"],"code":"`id`"},{"id":"/root/children/56/children/4","type":"text","loc":{"start":4551,"end":4557,"line":{"s":174,"e":174,"code":["| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |"]},"column":{"s":6,"e":12}},"dim":["","paragraph.56","text.4"],"code":"    | "},{"id":"/root/children/56/children/5","type":"inlineCode","loc":{"start":4557,"end":4565,"line":{"s":174,"e":174,"code":["| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |"]},"column":{"s":12,"e":20}},"dim":["","paragraph.56","inlineCode.5"],"code":"`row[0]`"},{"id":"/root/children/56/children/6","type":"text","loc":{"start":4565,"end":4689,"line":{"s":174,"e":175,"code":["| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |","| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":20,"e":2}},"dim":["","paragraph.56","text.6"],"code":" — the file path (fn)                  | Full filesystem path to the source file                                        |\n| "},{"id":"/root/children/56/children/7","type":"inlineCode","loc":{"start":4689,"end":4696,"line":{"s":175,"e":175,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.56","inlineCode.7"],"code":"`value`"},{"id":"/root/children/56/children/8","type":"text","loc":{"start":4696,"end":4699,"line":{"s":175,"e":175,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":9,"e":12}},"dim":["","paragraph.56","text.8"],"code":" | "},{"id":"/root/children/56/children/9","type":"inlineCode","loc":{"start":4699,"end":4707,"line":{"s":175,"e":175,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":12,"e":20}},"dim":["","paragraph.56","inlineCode.9"],"code":"`row[1]`"},{"id":"/root/children/56/children/10","type":"text","loc":{"start":4707,"end":4710,"line":{"s":175,"e":175,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":20,"e":23}},"dim":["","paragraph.56","text.10"],"code":" — "},{"id":"/root/children/56/children/11","type":"inlineCode","loc":{"start":4710,"end":4730,"line":{"s":175,"e":175,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":23,"e":43}},"dim":["","paragraph.56","inlineCode.11"],"code":"`JSON.parse(row[1])`"},{"id":"/root/children/56/children/12","type":"text","loc":{"start":4730,"end":4733,"line":{"s":175,"e":175,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":43,"e":46}},"dim":["","paragraph.56","text.12"],"code":" → "},{"id":"/root/children/56/children/13","type":"inlineCode","loc":{"start":4733,"end":4745,"line":{"s":175,"e":175,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |"]},"column":{"s":46,"e":58}},"dim":["","paragraph.56","inlineCode.13"],"code":"`data.flast`"},{"id":"/root/children/56/children/14","type":"text","loc":{"start":4745,"end":4831,"line":{"s":175,"e":176,"code":["| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |","| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |"]},"column":{"s":58,"e":2}},"dim":["","paragraph.56","text.14"],"code":" | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| "},{"id":"/root/children/56/children/15","type":"inlineCode","loc":{"start":4831,"end":4835,"line":{"s":176,"e":176,"code":["| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.56","inlineCode.15"],"code":"`mt`"},{"id":"/root/children/56/children/16","type":"text","loc":{"start":4835,"end":4841,"line":{"s":176,"e":176,"code":["| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |"]},"column":{"s":6,"e":12}},"dim":["","paragraph.56","text.16"],"code":"    | "},{"id":"/root/children/56/children/17","type":"inlineCode","loc":{"start":4841,"end":4849,"line":{"s":176,"e":176,"code":["| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |"]},"column":{"s":12,"e":20}},"dim":["","paragraph.56","inlineCode.17"],"code":"`row[2]`"},{"id":"/root/children/56/children/18","type":"text","loc":{"start":4849,"end":4970,"line":{"s":176,"e":176,"code":["| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |"]},"column":{"s":20,"e":141}},"dim":["","paragraph.56","text.18"],"code":" — file modification timestamp         | ISO timestamp of last file modification                                        |"},{"id":"/root/children/57","type":"paragraph","loc":{"start":4972,"end":5236,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":0,"e":264}},"dim":["","paragraph.57"],"code":"**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."},{"id":"/root/children/57/children/0","type":"strong","loc":{"start":4972,"end":4995,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":0,"e":23}},"dim":["","paragraph.57","strong.0"],"code":"**How it's populated:**"},{"id":"/root/children/57/children/0/children/0","type":"text","loc":{"start":4974,"end":4993,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":2,"e":21}},"dim":["","paragraph.57","strong.0","text.0"],"code":"How it's populated:"},{"id":"/root/children/57/children/1","type":"text","loc":{"start":4995,"end":5007,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":23,"e":35}},"dim":["","paragraph.57","text.1"],"code":" Written by "},{"id":"/root/children/57/children/2","type":"inlineCode","loc":{"start":5007,"end":5025,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":35,"e":53}},"dim":["","paragraph.57","inlineCode.2"],"code":"`astNavigator.mjs`"},{"id":"/root/children/57/children/3","type":"text","loc":{"start":5025,"end":5030,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":53,"e":58}},"dim":["","paragraph.57","text.3"],"code":" via "},{"id":"/root/children/57/children/4","type":"inlineCode","loc":{"start":5030,"end":5054,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":58,"e":82}},"dim":["","paragraph.57","inlineCode.4"],"code":"`cacheFlast(fnIn, data)`"},{"id":"/root/children/57/children/5","type":"text","loc":{"start":5054,"end":5074,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":82,"e":102}},"dim":["","paragraph.57","text.5"],"code":" (line 1571), using "},{"id":"/root/children/57/children/6","type":"inlineCode","loc":{"start":5074,"end":5091,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":102,"e":119}},"dim":["","paragraph.57","inlineCode.6"],"code":"`initSqliteCache`"},{"id":"/root/children/57/children/7","type":"text","loc":{"start":5091,"end":5097,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":119,"e":125}},"dim":["","paragraph.57","text.7"],"code":" from "},{"id":"/root/children/57/children/8","type":"inlineCode","loc":{"start":5097,"end":5109,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":125,"e":137}},"dim":["","paragraph.57","inlineCode.8"],"code":"`sqlito.mjs`"},{"id":"/root/children/57/children/9","type":"text","loc":{"start":5109,"end":5115,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":137,"e":143}},"dim":["","paragraph.57","text.9"],"code":". The "},{"id":"/root/children/57/children/10","type":"inlineCode","loc":{"start":5115,"end":5127,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":143,"e":155}},"dim":["","paragraph.57","inlineCode.10"],"code":"`data.value`"},{"id":"/root/children/57/children/11","type":"text","loc":{"start":5127,"end":5182,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":155,"e":210}},"dim":["","paragraph.57","text.11"],"code":" is the array of fragments produced by extractors like "},{"id":"/root/children/57/children/12","type":"inlineCode","loc":{"start":5182,"end":5204,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":210,"e":232}},"dim":["","paragraph.57","inlineCode.12"],"code":"`extractFlastMarkdown`"},{"id":"/root/children/57/children/13","type":"text","loc":{"start":5204,"end":5206,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":232,"e":234}},"dim":["","paragraph.57","text.13"],"code":", "},{"id":"/root/children/57/children/14","type":"inlineCode","loc":{"start":5206,"end":5230,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":234,"e":258}},"dim":["","paragraph.57","inlineCode.14"],"code":"`extractFlastJavascript`"},{"id":"/root/children/57/children/15","type":"text","loc":{"start":5230,"end":5236,"line":{"s":178,"e":178,"code":["**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc."]},"column":{"s":258,"e":264}},"dim":["","paragraph.57","text.15"],"code":", etc."},{"id":"/root/children/58","type":"paragraph","loc":{"start":5238,"end":5280,"line":{"s":180,"e":180,"code":["**Query pattern** (in `actualizeNodes()`):"]},"column":{"s":0,"e":42}},"dim":["","paragraph.58"],"code":"**Query pattern** (in `actualizeNodes()`):"},{"id":"/root/children/58/children/0","type":"strong","loc":{"start":5238,"end":5255,"line":{"s":180,"e":180,"code":["**Query pattern** (in `actualizeNodes()`):"]},"column":{"s":0,"e":17}},"dim":["","paragraph.58","strong.0"],"code":"**Query pattern**"},{"id":"/root/children/58/children/0/children/0","type":"text","loc":{"start":5240,"end":5253,"line":{"s":180,"e":180,"code":["**Query pattern** (in `actualizeNodes()`):"]},"column":{"s":2,"e":15}},"dim":["","paragraph.58","strong.0","text.0"],"code":"Query pattern"},{"id":"/root/children/58/children/1","type":"text","loc":{"start":5255,"end":5260,"line":{"s":180,"e":180,"code":["**Query pattern** (in `actualizeNodes()`):"]},"column":{"s":17,"e":22}},"dim":["","paragraph.58","text.1"],"code":" (in "},{"id":"/root/children/58/children/2","type":"inlineCode","loc":{"start":5260,"end":5278,"line":{"s":180,"e":180,"code":["**Query pattern** (in `actualizeNodes()`):"]},"column":{"s":22,"e":40}},"dim":["","paragraph.58","inlineCode.2"],"code":"`actualizeNodes()`"},{"id":"/root/children/58/children/3","type":"text","loc":{"start":5278,"end":5280,"line":{"s":180,"e":180,"code":["**Query pattern** (in `actualizeNodes()`):"]},"column":{"s":40,"e":42}},"dim":["","paragraph.58","text.3"],"code":"):"},{"id":"/root/children/59","type":"code","loc":{"start":5282,"end":5372,"line":{"s":182,"e":184,"code":["```sql","SELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>","```"]},"column":{"s":0,"e":3}},"dim":["","code.59"],"code":"```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```","symbName":"code","symbRange":[5374,5489],"symbRangeL":[null,189],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>"},{"id":"/root/children/60","type":"paragraph","loc":{"start":5374,"end":5423,"line":{"s":186,"e":186,"code":["Only changed files (newer mtime) are reprocessed."]},"column":{"s":0,"e":49}},"dim":["","paragraph.60"],"code":"Only changed files (newer mtime) are reprocessed."},{"id":"/root/children/60/children/0","type":"text","loc":{"start":5374,"end":5423,"line":{"s":186,"e":186,"code":["Only changed files (newer mtime) are reprocessed."]},"column":{"s":0,"e":49}},"dim":["","paragraph.60","text.0"],"code":"Only changed files (newer mtime) are reprocessed."},{"id":"/root/children/61","type":"paragraph","loc":{"start":5425,"end":5489,"line":{"s":188,"e":188,"code":["**Node value structure** (each element in the JSON flast array):"]},"column":{"s":0,"e":64}},"dim":["","paragraph.61"],"code":"**Node value structure** (each element in the JSON flast array):"},{"id":"/root/children/61/children/0","type":"strong","loc":{"start":5425,"end":5449,"line":{"s":188,"e":188,"code":["**Node value structure** (each element in the JSON flast array):"]},"column":{"s":0,"e":24}},"dim":["","paragraph.61","strong.0"],"code":"**Node value structure**"},{"id":"/root/children/61/children/0/children/0","type":"text","loc":{"start":5427,"end":5447,"line":{"s":188,"e":188,"code":["**Node value structure** (each element in the JSON flast array):"]},"column":{"s":2,"e":22}},"dim":["","paragraph.61","strong.0","text.0"],"code":"Node value structure"},{"id":"/root/children/61/children/1","type":"text","loc":{"start":5449,"end":5489,"line":{"s":188,"e":188,"code":["**Node value structure** (each element in the JSON flast array):"]},"column":{"s":24,"e":64}},"dim":["","paragraph.61","text.1"],"code":" (each element in the JSON flast array):"},{"id":"/root/children/62","type":"code","loc":{"start":5491,"end":5767,"line":{"s":190,"e":200,"code":["```json","{","  \"id\": \"node-id\",","  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],","  \"type\": \"heading|link|code|paragraph|list|...\",","  \"code\": \"raw text content\",","  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },","  \"symbName\": null,","  \"symbRangeL\": null","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.62"],"code":"```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```","symbName":"code","symbRange":[5769,7967],"symbRangeL":[null,220],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>"},{"id":"/root/children/63","type":"thematicBreak","loc":{"start":5769,"end":5772,"line":{"s":202,"e":202,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.63"],"code":"---"},{"id":"/root/children/64","type":"heading","loc":{"start":5774,"end":5800,"line":{"s":204,"e":204,"code":["### `cache_ast_lake_nodes`"]},"column":{"s":0,"e":26}},"dim":["","heading.64"],"code":"### `cache_ast_lake_nodes`","symbName":"heading","symbRange":[5802,9299],"symbRangeL":[204,258],"outerCode":"\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):\n\n- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):\n\n```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```\n\n---","outerHtml":"\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>\n\n<ul><li><b>headings</b>: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops</li><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>\n\n<p>```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```</p>\n\n<hr/>"},{"id":"/root/children/64/children/0","type":"inlineCode","loc":{"start":5778,"end":5800,"line":{"s":204,"e":204,"code":["### `cache_ast_lake_nodes`"]},"column":{"s":4,"e":26}},"dim":["","heading.64","inlineCode.0"],"code":"`cache_ast_lake_nodes`"},{"id":"/root/children/65","type":"paragraph","loc":{"start":5802,"end":6055,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":0,"e":253}},"dim":["","paragraph.65"],"code":"**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."},{"id":"/root/children/65/children/0","type":"strong","loc":{"start":5802,"end":5811,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":0,"e":9}},"dim":["","paragraph.65","strong.0"],"code":"**Role:**"},{"id":"/root/children/65/children/0/children/0","type":"text","loc":{"start":5804,"end":5809,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":2,"e":7}},"dim":["","paragraph.65","strong.0","text.0"],"code":"Role:"},{"id":"/root/children/65/children/1","type":"text","loc":{"start":5811,"end":5881,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":9,"e":79}},"dim":["","paragraph.65","text.1"],"code":" Individual AST node storage — one row per symbolic node. Produced by "},{"id":"/root/children/65/children/2","type":"inlineCode","loc":{"start":5881,"end":5899,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":79,"e":97}},"dim":["","paragraph.65","inlineCode.2"],"code":"`normalizeNodes()`"},{"id":"/root/children/65/children/3","type":"text","loc":{"start":5899,"end":5903,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":97,"e":101}},"dim":["","paragraph.65","text.3"],"code":" in "},{"id":"/root/children/65/children/4","type":"inlineCode","loc":{"start":5903,"end":5921,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":101,"e":119}},"dim":["","paragraph.65","inlineCode.4"],"code":"`actualizeNodes()`"},{"id":"/root/children/65/children/5","type":"text","loc":{"start":5921,"end":5966,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":119,"e":164}},"dim":["","paragraph.65","text.5"],"code":" (ast-lake.mjs:183-266), which extracts only "},{"id":"/root/children/65/children/6","type":"inlineCode","loc":{"start":5966,"end":5981,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":164,"e":179}},"dim":["","paragraph.65","inlineCode.6"],"code":"`symbolicTypes`"},{"id":"/root/children/65/children/7","type":"text","loc":{"start":5981,"end":6055,"line":{"s":206,"e":206,"code":["**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast."]},"column":{"s":179,"e":253}},"dim":["","paragraph.65","text.7"],"code":" (heading, link, comment, FunctionDeclaration, Identifier) from the flast."},{"id":"/root/children/66","type":"paragraph","loc":{"start":6057,"end":7896,"line":{"s":208,"e":217,"code":["| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |","| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |","| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |","| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |","| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |","| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |","| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |","| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |","| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |","| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"]},"column":{"s":0,"e":183}},"dim":["","paragraph.66"],"code":"| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"},{"id":"/root/children/66/children/0","type":"text","loc":{"start":6057,"end":6427,"line":{"s":208,"e":210,"code":["| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |","| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |","| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.66","text.0"],"code":"| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/66/children/1","type":"inlineCode","loc":{"start":6427,"end":6431,"line":{"s":210,"e":210,"code":["| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.66","inlineCode.1"],"code":"`id`"},{"id":"/root/children/66/children/2","type":"text","loc":{"start":6431,"end":6437,"line":{"s":210,"e":210,"code":["| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |"]},"column":{"s":6,"e":12}},"dim":["","paragraph.66","text.2"],"code":"    | "},{"id":"/root/children/66/children/3","type":"inlineCode","loc":{"start":6437,"end":6470,"line":{"s":210,"e":210,"code":["| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |"]},"column":{"s":12,"e":45}},"dim":["","paragraph.66","inlineCode.3"],"code":"`` `${node.fn}:${node.offset}` ``"},{"id":"/root/children/66/children/4","type":"text","loc":{"start":6470,"end":6511,"line":{"s":210,"e":210,"code":["| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |"]},"column":{"s":45,"e":86}},"dim":["","paragraph.66","text.4"],"code":"         | File path + offset position → "},{"id":"/root/children/66/children/5","type":"inlineCode","loc":{"start":6511,"end":6534,"line":{"s":210,"e":210,"code":["| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |"]},"column":{"s":86,"e":109}},"dim":["","paragraph.66","inlineCode.5"],"code":"`buildNodeAbsoluteId()`"},{"id":"/root/children/66/children/6","type":"text","loc":{"start":6534,"end":6611,"line":{"s":210,"e":211,"code":["| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |","| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |"]},"column":{"s":109,"e":2}},"dim":["","paragraph.66","text.6"],"code":"                                                                         |\n| "},{"id":"/root/children/66/children/7","type":"inlineCode","loc":{"start":6611,"end":6618,"line":{"s":211,"e":211,"code":["| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.66","inlineCode.7"],"code":"`value`"},{"id":"/root/children/66/children/8","type":"text","loc":{"start":6618,"end":6621,"line":{"s":211,"e":211,"code":["| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |"]},"column":{"s":9,"e":12}},"dim":["","paragraph.66","text.8"],"code":" | "},{"id":"/root/children/66/children/9","type":"inlineCode","loc":{"start":6621,"end":6643,"line":{"s":211,"e":211,"code":["| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |"]},"column":{"s":12,"e":34}},"dim":["","paragraph.66","inlineCode.9"],"code":"`JSON.stringify(node)`"},{"id":"/root/children/66/children/10","type":"text","loc":{"start":6643,"end":6795,"line":{"s":211,"e":212,"code":["| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |","| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |"]},"column":{"s":34,"e":2}},"dim":["","paragraph.66","text.10"],"code":"                    | Full normalized node as JSON (see structure below)                                                                            |\n| "},{"id":"/root/children/66/children/11","type":"inlineCode","loc":{"start":6795,"end":6799,"line":{"s":212,"e":212,"code":["| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.66","inlineCode.11"],"code":"`mt`"},{"id":"/root/children/66/children/12","type":"text","loc":{"start":6799,"end":6805,"line":{"s":212,"e":212,"code":["| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |"]},"column":{"s":6,"e":12}},"dim":["","paragraph.66","text.12"],"code":"    | "},{"id":"/root/children/66/children/13","type":"inlineCode","loc":{"start":6805,"end":6814,"line":{"s":212,"e":212,"code":["| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |"]},"column":{"s":12,"e":21}},"dim":["","paragraph.66","inlineCode.13"],"code":"`node.mt`"},{"id":"/root/children/66/children/14","type":"text","loc":{"start":6814,"end":6979,"line":{"s":212,"e":213,"code":["| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |","| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":21,"e":2}},"dim":["","paragraph.66","text.14"],"code":"                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| "},{"id":"/root/children/66/children/15","type":"inlineCode","loc":{"start":6979,"end":6984,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.66","inlineCode.15"],"code":"`sem`"},{"id":"/root/children/66/children/16","type":"text","loc":{"start":6984,"end":6989,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":7,"e":12}},"dim":["","paragraph.66","text.16"],"code":"   | "},{"id":"/root/children/66/children/17","type":"inlineCode","loc":{"start":6989,"end":7000,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":12,"e":23}},"dim":["","paragraph.66","inlineCode.17"],"code":"`node.type`"},{"id":"/root/children/66/children/18","type":"text","loc":{"start":7000,"end":7044,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":23,"e":67}},"dim":["","paragraph.66","text.18"],"code":"                               | Node type: "},{"id":"/root/children/66/children/19","type":"inlineCode","loc":{"start":7044,"end":7055,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":67,"e":78}},"dim":["","paragraph.66","inlineCode.19"],"code":"`\"heading\"`"},{"id":"/root/children/66/children/20","type":"text","loc":{"start":7055,"end":7057,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":78,"e":80}},"dim":["","paragraph.66","text.20"],"code":", "},{"id":"/root/children/66/children/21","type":"inlineCode","loc":{"start":7057,"end":7065,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":80,"e":88}},"dim":["","paragraph.66","inlineCode.21"],"code":"`\"link\"`"},{"id":"/root/children/66/children/22","type":"text","loc":{"start":7065,"end":7067,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":88,"e":90}},"dim":["","paragraph.66","text.22"],"code":", "},{"id":"/root/children/66/children/23","type":"inlineCode","loc":{"start":7067,"end":7078,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":90,"e":101}},"dim":["","paragraph.66","inlineCode.23"],"code":"`\"comment\"`"},{"id":"/root/children/66/children/24","type":"text","loc":{"start":7078,"end":7080,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":101,"e":103}},"dim":["","paragraph.66","text.24"],"code":", "},{"id":"/root/children/66/children/25","type":"inlineCode","loc":{"start":7080,"end":7103,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":103,"e":126}},"dim":["","paragraph.66","inlineCode.25"],"code":"`\"FunctionDeclaration\"`"},{"id":"/root/children/66/children/26","type":"text","loc":{"start":7103,"end":7105,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":126,"e":128}},"dim":["","paragraph.66","text.26"],"code":", "},{"id":"/root/children/66/children/27","type":"inlineCode","loc":{"start":7105,"end":7119,"line":{"s":213,"e":213,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |"]},"column":{"s":128,"e":142}},"dim":["","paragraph.66","inlineCode.27"],"code":"`\"Identifier\"`"},{"id":"/root/children/66/children/28","type":"text","loc":{"start":7119,"end":7163,"line":{"s":213,"e":214,"code":["| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |","| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |"]},"column":{"s":142,"e":2}},"dim":["","paragraph.66","text.28"],"code":"                                        |\n| "},{"id":"/root/children/66/children/29","type":"inlineCode","loc":{"start":7163,"end":7169,"line":{"s":214,"e":214,"code":["| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.66","inlineCode.29"],"code":"`num1`"},{"id":"/root/children/66/children/30","type":"text","loc":{"start":7169,"end":7173,"line":{"s":214,"e":214,"code":["| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |"]},"column":{"s":8,"e":12}},"dim":["","paragraph.66","text.30"],"code":"  | "},{"id":"/root/children/66/children/31","type":"inlineCode","loc":{"start":7173,"end":7191,"line":{"s":214,"e":214,"code":["| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |"]},"column":{"s":12,"e":30}},"dim":["","paragraph.66","inlineCode.31"],"code":"`node.position[0]`"},{"id":"/root/children/66/children/32","type":"text","loc":{"start":7191,"end":7347,"line":{"s":214,"e":215,"code":["| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |","| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |"]},"column":{"s":30,"e":2}},"dim":["","paragraph.66","text.32"],"code":"                        | Line number in source file (1-based)                                                                                          |\n| "},{"id":"/root/children/66/children/33","type":"inlineCode","loc":{"start":7347,"end":7353,"line":{"s":215,"e":215,"code":["| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.66","inlineCode.33"],"code":"`num2`"},{"id":"/root/children/66/children/34","type":"text","loc":{"start":7353,"end":7357,"line":{"s":215,"e":215,"code":["| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |"]},"column":{"s":8,"e":12}},"dim":["","paragraph.66","text.34"],"code":"  | "},{"id":"/root/children/66/children/35","type":"inlineCode","loc":{"start":7357,"end":7375,"line":{"s":215,"e":215,"code":["| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |"]},"column":{"s":12,"e":30}},"dim":["","paragraph.66","inlineCode.35"],"code":"`node.position[1]`"},{"id":"/root/children/66/children/36","type":"text","loc":{"start":7375,"end":7531,"line":{"s":215,"e":216,"code":["| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |","| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |"]},"column":{"s":30,"e":2}},"dim":["","paragraph.66","text.36"],"code":"                        | Column number in source file (1-based)                                                                                        |\n| "},{"id":"/root/children/66/children/37","type":"inlineCode","loc":{"start":7531,"end":7536,"line":{"s":216,"e":216,"code":["| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.66","inlineCode.37"],"code":"`ref`"},{"id":"/root/children/66/children/38","type":"text","loc":{"start":7536,"end":7541,"line":{"s":216,"e":216,"code":["| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |"]},"column":{"s":7,"e":12}},"dim":["","paragraph.66","text.38"],"code":"   | "},{"id":"/root/children/66/children/39","type":"inlineCode","loc":{"start":7541,"end":7563,"line":{"s":216,"e":216,"code":["| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |"]},"column":{"s":12,"e":34}},"dim":["","paragraph.66","inlineCode.39"],"code":"`buildNodeRefId(node)`"},{"id":"/root/children/66/children/40","type":"text","loc":{"start":7563,"end":7715,"line":{"s":216,"e":217,"code":["| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |","| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"]},"column":{"s":34,"e":2}},"dim":["","paragraph.66","text.40"],"code":"                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| "},{"id":"/root/children/66/children/41","type":"inlineCode","loc":{"start":7715,"end":7721,"line":{"s":217,"e":217,"code":["| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.66","inlineCode.41"],"code":"`body`"},{"id":"/root/children/66/children/42","type":"text","loc":{"start":7721,"end":7725,"line":{"s":217,"e":217,"code":["| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"]},"column":{"s":8,"e":12}},"dim":["","paragraph.66","text.42"],"code":"  | "},{"id":"/root/children/66/children/43","type":"inlineCode","loc":{"start":7725,"end":7764,"line":{"s":217,"e":217,"code":["| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"]},"column":{"s":12,"e":51}},"dim":["","paragraph.66","inlineCode.43"],"code":"`node.outerCode \\|\\| node.code \\|\\| ''`"},{"id":"/root/children/66/children/44","type":"text","loc":{"start":7764,"end":7896,"line":{"s":217,"e":217,"code":["| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"]},"column":{"s":51,"e":183}},"dim":["","paragraph.66","text.44"],"code":"   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |"},{"id":"/root/children/67","type":"paragraph","loc":{"start":7898,"end":7967,"line":{"s":219,"e":219,"code":["**Normalized node value structure** (the JSON in the `value` column):"]},"column":{"s":0,"e":69}},"dim":["","paragraph.67"],"code":"**Normalized node value structure** (the JSON in the `value` column):"},{"id":"/root/children/67/children/0","type":"strong","loc":{"start":7898,"end":7933,"line":{"s":219,"e":219,"code":["**Normalized node value structure** (the JSON in the `value` column):"]},"column":{"s":0,"e":35}},"dim":["","paragraph.67","strong.0"],"code":"**Normalized node value structure**"},{"id":"/root/children/67/children/0/children/0","type":"text","loc":{"start":7900,"end":7931,"line":{"s":219,"e":219,"code":["**Normalized node value structure** (the JSON in the `value` column):"]},"column":{"s":2,"e":33}},"dim":["","paragraph.67","strong.0","text.0"],"code":"Normalized node value structure"},{"id":"/root/children/67/children/1","type":"text","loc":{"start":7933,"end":7951,"line":{"s":219,"e":219,"code":["**Normalized node value structure** (the JSON in the `value` column):"]},"column":{"s":35,"e":53}},"dim":["","paragraph.67","text.1"],"code":" (the JSON in the "},{"id":"/root/children/67/children/2","type":"inlineCode","loc":{"start":7951,"end":7958,"line":{"s":219,"e":219,"code":["**Normalized node value structure** (the JSON in the `value` column):"]},"column":{"s":53,"e":60}},"dim":["","paragraph.67","inlineCode.2"],"code":"`value`"},{"id":"/root/children/67/children/3","type":"text","loc":{"start":7958,"end":7967,"line":{"s":219,"e":219,"code":["**Normalized node value structure** (the JSON in the `value` column):"]},"column":{"s":60,"e":69}},"dim":["","paragraph.67","text.3"],"code":" column):"},{"id":"/root/children/68","type":"code","loc":{"start":7969,"end":8428,"line":{"s":221,"e":238,"code":["```json","{","  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",","  \"trail\": [\"getting-started\", \"installation\"],","  \"type\": \"heading\",","  \"nomen\": \"Installation\",","  \"code\": \"## Installation\",","  \"symbName\": null,","  \"symbBlame\": [\"abc123def\", \"456ghi789\"],","  \"outerCode\": \"full subtree text...\",","  \"position\": [42, 1],","  \"lineRange\": [42, 85],","  \"offset\": 42,","  \"symbMt\": \"2025-06-15T10:30:00Z\",","  \"symbUser\": \"author@example.com\",","  \"mt\": \"2025-06-15T10:30:00Z\"","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.68"],"code":"```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```","symbName":"code","symbRange":[8430,9089],"symbRangeL":[null,248],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):\n\n- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>\n\n<ul><li><b>headings</b>: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops</li><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>"},{"id":"/root/children/69","type":"paragraph","loc":{"start":8430,"end":8506,"line":{"s":240,"e":240,"code":["**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):"]},"column":{"s":0,"e":76}},"dim":["","paragraph.69"],"code":"**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):"},{"id":"/root/children/69/children/0","type":"strong","loc":{"start":8430,"end":8459,"line":{"s":240,"e":240,"code":["**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):"]},"column":{"s":0,"e":29}},"dim":["","paragraph.69","strong.0"],"code":"**Key normalization details**"},{"id":"/root/children/69/children/0/children/0","type":"text","loc":{"start":8432,"end":8457,"line":{"s":240,"e":240,"code":["**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):"]},"column":{"s":2,"e":27}},"dim":["","paragraph.69","strong.0","text.0"],"code":"Key normalization details"},{"id":"/root/children/69/children/1","type":"text","loc":{"start":8459,"end":8466,"line":{"s":240,"e":240,"code":["**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):"]},"column":{"s":29,"e":36}},"dim":["","paragraph.69","text.1"],"code":" (from "},{"id":"/root/children/69/children/2","type":"inlineCode","loc":{"start":8466,"end":8484,"line":{"s":240,"e":240,"code":["**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):"]},"column":{"s":36,"e":54}},"dim":["","paragraph.69","inlineCode.2"],"code":"`normalizeNodes()`"},{"id":"/root/children/69/children/3","type":"text","loc":{"start":8484,"end":8506,"line":{"s":240,"e":240,"code":["**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):"]},"column":{"s":54,"e":76}},"dim":["","paragraph.69","text.3"],"code":" at ast-lake.mjs:183):"},{"id":"/root/children/70","type":"list","loc":{"start":8508,"end":9050,"line":{"s":242,"e":245,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops","- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label","- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author","- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"]},"column":{"s":0,"e":134}},"dim":["","list.70"],"code":"- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out","symbName":"list","symbRange":[9052,10948],"symbRangeL":[242,271],"outerCode":"- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):\n\n```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```\n\n---\n\n### `cache_ast_lake_nodes_ref`\n\n**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.\n\n| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |\n\n**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):","outerHtml":"<ul><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>\n\n<p>```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_ref`</h3>\n\n<p><b>Role:</b> Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.</p>\n\n<p>| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | <b>Readable refId</b> — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |</p>\n\n<p><b>`buildNodeAbsoluteId()` construction</b> (ast-lake.mjs:29-44):</p>"},{"id":"/root/children/70/children/0","type":"listItem","loc":{"start":8508,"end":8675,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":0,"e":167}},"dim":["","list.70","listItem.0"],"code":"- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"},{"id":"/root/children/70/children/0/children/0","type":"paragraph","loc":{"start":8510,"end":8675,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":2,"e":167}},"dim":["","list.70","listItem.0","paragraph.0"],"code":"**headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"},{"id":"/root/children/70/children/0/children/0/children/0","type":"strong","loc":{"start":8510,"end":8522,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":2,"e":14}},"dim":["","list.70","listItem.0","paragraph.0","strong.0"],"code":"**headings**"},{"id":"/root/children/70/children/0/children/0/children/0/children/0","type":"text","loc":{"start":8512,"end":8520,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":4,"e":12}},"dim":["","list.70","listItem.0","paragraph.0","strong.0","text.0"],"code":"headings"},{"id":"/root/children/70/children/0/children/0/children/1","type":"text","loc":{"start":8522,"end":8524,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":14,"e":16}},"dim":["","list.70","listItem.0","paragraph.0","text.1"],"code":": "},{"id":"/root/children/70/children/0/children/0/children/2","type":"inlineCode","loc":{"start":8524,"end":8542,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":16,"e":34}},"dim":["","list.70","listItem.0","paragraph.0","inlineCode.2"],"code":"`processHeading()`"},{"id":"/root/children/70/children/0/children/0/children/3","type":"text","loc":{"start":8542,"end":8608,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":34,"e":100}},"dim":["","list.70","listItem.0","paragraph.0","text.3"],"code":" extracts level and pure text; breadcrumb trail tracks hierarchy; "},{"id":"/root/children/70/children/0/children/0/children/4","type":"inlineCode","loc":{"start":8608,"end":8634,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":100,"e":126}},"dim":["","list.70","listItem.0","paragraph.0","inlineCode.4"],"code":"`currentLevel > lastLevel`"},{"id":"/root/children/70/children/0/children/0/children/5","type":"text","loc":{"start":8634,"end":8643,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":126,"e":135}},"dim":["","list.70","listItem.0","paragraph.0","text.5"],"code":" pushes, "},{"id":"/root/children/70/children/0/children/0/children/6","type":"inlineCode","loc":{"start":8643,"end":8670,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":135,"e":162}},"dim":["","list.70","listItem.0","paragraph.0","inlineCode.6"],"code":"`currentLevel <= lastLevel`"},{"id":"/root/children/70/children/0/children/0/children/7","type":"text","loc":{"start":8670,"end":8675,"line":{"s":242,"e":242,"code":["- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops"]},"column":{"s":162,"e":167}},"dim":["","list.70","listItem.0","paragraph.0","text.7"],"code":" pops"},{"id":"/root/children/70/children/1","type":"listItem","loc":{"start":8676,"end":8768,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":0,"e":92}},"dim":["","list.70","listItem.1"],"code":"- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"},{"id":"/root/children/70/children/1/children/0","type":"paragraph","loc":{"start":8678,"end":8768,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":2,"e":92}},"dim":["","list.70","listItem.1","paragraph.0"],"code":"**links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"},{"id":"/root/children/70/children/1/children/0/children/0","type":"strong","loc":{"start":8678,"end":8687,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":2,"e":11}},"dim":["","list.70","listItem.1","paragraph.0","strong.0"],"code":"**links**"},{"id":"/root/children/70/children/1/children/0/children/0/children/0","type":"text","loc":{"start":8680,"end":8685,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":4,"e":9}},"dim":["","list.70","listItem.1","paragraph.0","strong.0","text.0"],"code":"links"},{"id":"/root/children/70/children/1/children/0/children/1","type":"text","loc":{"start":8687,"end":8689,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":11,"e":13}},"dim":["","list.70","listItem.1","paragraph.0","text.1"],"code":": "},{"id":"/root/children/70/children/1/children/0/children/2","type":"inlineCode","loc":{"start":8689,"end":8710,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":13,"e":34}},"dim":["","list.70","listItem.1","paragraph.0","inlineCode.2"],"code":"`parseMarkdownLink()`"},{"id":"/root/children/70/children/1/children/0/children/3","type":"text","loc":{"start":8710,"end":8720,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":34,"e":44}},"dim":["","list.70","listItem.1","paragraph.0","text.3"],"code":" extracts "},{"id":"/root/children/70/children/1/children/0/children/4","type":"inlineCode","loc":{"start":8720,"end":8734,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":44,"e":58}},"dim":["","list.70","listItem.1","paragraph.0","inlineCode.4"],"code":"`[label](url)`"},{"id":"/root/children/70/children/1/children/0/children/5","type":"text","loc":{"start":8734,"end":8736,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":58,"e":60}},"dim":["","list.70","listItem.1","paragraph.0","text.5"],"code":"; "},{"id":"/root/children/70/children/1/children/0/children/6","type":"inlineCode","loc":{"start":8736,"end":8747,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":60,"e":71}},"dim":["","list.70","listItem.1","paragraph.0","inlineCode.6"],"code":"`outerCode`"},{"id":"/root/children/70/children/1/children/0/children/7","type":"text","loc":{"start":8747,"end":8768,"line":{"s":243,"e":243,"code":["- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label"]},"column":{"s":71,"e":92}},"dim":["","list.70","listItem.1","paragraph.0","text.7"],"code":" = url; nomen = label"},{"id":"/root/children/70/children/2","type":"listItem","loc":{"start":8769,"end":8915,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":0,"e":146}},"dim":["","list.70","listItem.2"],"code":"- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"},{"id":"/root/children/70/children/2/children/0","type":"paragraph","loc":{"start":8771,"end":8915,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":2,"e":146}},"dim":["","list.70","listItem.2","paragraph.0"],"code":"**git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"},{"id":"/root/children/70/children/2/children/0/children/0","type":"strong","loc":{"start":8771,"end":8784,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":2,"e":15}},"dim":["","list.70","listItem.2","paragraph.0","strong.0"],"code":"**git blame**"},{"id":"/root/children/70/children/2/children/0/children/0/children/0","type":"text","loc":{"start":8773,"end":8782,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":4,"e":13}},"dim":["","list.70","listItem.2","paragraph.0","strong.0","text.0"],"code":"git blame"},{"id":"/root/children/70/children/2/children/0/children/1","type":"text","loc":{"start":8784,"end":8786,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":15,"e":17}},"dim":["","list.70","listItem.2","paragraph.0","text.1"],"code":": "},{"id":"/root/children/70/children/2/children/0/children/2","type":"inlineCode","loc":{"start":8786,"end":8797,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":17,"e":28}},"dim":["","list.70","listItem.2","paragraph.0","inlineCode.2"],"code":"`symbBlame`"},{"id":"/root/children/70/children/2/children/0/children/3","type":"text","loc":{"start":8797,"end":8834,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":28,"e":65}},"dim":["","list.70","listItem.2","paragraph.0","text.3"],"code":" array of commit hashes for lines in "},{"id":"/root/children/70/children/2/children/0/children/4","type":"inlineCode","loc":{"start":8834,"end":8846,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":65,"e":77}},"dim":["","list.70","listItem.2","paragraph.0","inlineCode.4"],"code":"`symbRangeL`"},{"id":"/root/children/70/children/2/children/0/children/5","type":"text","loc":{"start":8846,"end":8848,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":77,"e":79}},"dim":["","list.70","listItem.2","paragraph.0","text.5"],"code":"; "},{"id":"/root/children/70/children/2/children/0/children/6","type":"inlineCode","loc":{"start":8848,"end":8856,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":79,"e":87}},"dim":["","list.70","listItem.2","paragraph.0","inlineCode.6"],"code":"`symbMt`"},{"id":"/root/children/70/children/2/children/0/children/7","type":"text","loc":{"start":8856,"end":8884,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":87,"e":115}},"dim":["","list.70","listItem.2","paragraph.0","text.7"],"code":" = most recent blame mtime; "},{"id":"/root/children/70/children/2/children/0/children/8","type":"inlineCode","loc":{"start":8884,"end":8894,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":115,"e":125}},"dim":["","list.70","listItem.2","paragraph.0","inlineCode.8"],"code":"`symbUser`"},{"id":"/root/children/70/children/2/children/0/children/9","type":"text","loc":{"start":8894,"end":8915,"line":{"s":244,"e":244,"code":["- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author"]},"column":{"s":125,"e":146}},"dim":["","list.70","listItem.2","paragraph.0","text.9"],"code":" = most recent author"},{"id":"/root/children/70/children/3","type":"listItem","loc":{"start":8916,"end":9050,"line":{"s":245,"e":245,"code":["- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"]},"column":{"s":0,"e":134}},"dim":["","list.70","listItem.3"],"code":"- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"},{"id":"/root/children/70/children/3/children/0","type":"paragraph","loc":{"start":8918,"end":9050,"line":{"s":245,"e":245,"code":["- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"]},"column":{"s":2,"e":134}},"dim":["","list.70","listItem.3","paragraph.0"],"code":"Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"},{"id":"/root/children/70/children/3/children/0/children/0","type":"text","loc":{"start":8918,"end":8943,"line":{"s":245,"e":245,"code":["- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"]},"column":{"s":2,"e":27}},"dim":["","list.70","listItem.3","paragraph.0","text.0"],"code":"Only nodes with types in "},{"id":"/root/children/70/children/3/children/0/children/1","type":"inlineCode","loc":{"start":8943,"end":8958,"line":{"s":245,"e":245,"code":["- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"]},"column":{"s":27,"e":42}},"dim":["","list.70","listItem.3","paragraph.0","inlineCode.1"],"code":"`symbolicTypes`"},{"id":"/root/children/70/children/3/children/0/children/2","type":"text","loc":{"start":8958,"end":9050,"line":{"s":245,"e":245,"code":["- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"]},"column":{"s":42,"e":134}},"dim":["","list.70","listItem.3","paragraph.0","text.2"],"code":" are kept; all others (paragraph, list, code without heading context, etc.) are filtered out"},{"id":"/root/children/71","type":"paragraph","loc":{"start":9052,"end":9089,"line":{"s":247,"e":247,"code":["**Query pattern** (voting dashboard):"]},"column":{"s":0,"e":37}},"dim":["","paragraph.71"],"code":"**Query pattern** (voting dashboard):"},{"id":"/root/children/71/children/0","type":"strong","loc":{"start":9052,"end":9069,"line":{"s":247,"e":247,"code":["**Query pattern** (voting dashboard):"]},"column":{"s":0,"e":17}},"dim":["","paragraph.71","strong.0"],"code":"**Query pattern**"},{"id":"/root/children/71/children/0/children/0","type":"text","loc":{"start":9054,"end":9067,"line":{"s":247,"e":247,"code":["**Query pattern** (voting dashboard):"]},"column":{"s":2,"e":15}},"dim":["","paragraph.71","strong.0","text.0"],"code":"Query pattern"},{"id":"/root/children/71/children/1","type":"text","loc":{"start":9069,"end":9089,"line":{"s":247,"e":247,"code":["**Query pattern** (voting dashboard):"]},"column":{"s":17,"e":37}},"dim":["","paragraph.71","text.1"],"code":" (voting dashboard):"},{"id":"/root/children/72","type":"code","loc":{"start":9091,"end":9294,"line":{"s":249,"e":255,"code":["```sql","SELECT n.id, n.body, n.num1 as headingLevel, r.ref","FROM cache_ast_lake_nodes n","INNER JOIN cache_ast_lake_nodes_ref r USING(id)","INNER JOIN repo_voting v ON v.ref = r.ref","WHERE n.sem = 'heading'","```"]},"column":{"s":0,"e":3}},"dim":["","code.72"],"code":"```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```","symbName":"code","symbRange":[9296,11887],"symbRangeL":[null,290],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):\n\n- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):\n\n```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```\n\n---\n\n### `cache_ast_lake_nodes_ref`\n\n**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.\n\n| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |\n\n**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):\n\n1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`\n2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`\n\n**`buildNodeRefId()`** (ast-lake.mjs:46-48):\n\n- For headings: returns last trail segment (e.g. `\"installation\"`)\n- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`\n\n**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.\n\n---\n\n### `cache_ast_lake_nodes_backup`\n\n**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>\n\n<ul><li><b>headings</b>: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops</li><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>\n\n<p>```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_ref`</h3>\n\n<p><b>Role:</b> Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.</p>\n\n<p>| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | <b>Readable refId</b> — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |</p>\n\n<p><b>`buildNodeAbsoluteId()` construction</b> (ast-lake.mjs:29-44):</p>\n\n<ol><li>Strips `/repo/` prefix from fn → `relative/path/file.mdd`</li><li>Appends `::` + sanitized trail segments joined by `/`</li><li>For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor</li><li>Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`</li></ol>\n\n<p><b>`buildNodeRefId()`</b> (ast-lake.mjs:46-48):</p>\n\n<ul><li>For headings: returns last trail segment (e.g. `\"installation\"`)</li><li>For links: returns nomen (link label, e.g. `\"click here\"`)</li><li>This is the short ref stored in `cache_ast_lake_nodes.ref`</li></ul>\n\n<p><b>Why two ref tables?</b> `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_backup`</h3>\n\n<p><b>Role:</b> Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):</p>"},{"id":"/root/children/73","type":"thematicBreak","loc":{"start":9296,"end":9299,"line":{"s":257,"e":257,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.73"],"code":"---"},{"id":"/root/children/74","type":"heading","loc":{"start":9301,"end":9331,"line":{"s":259,"e":259,"code":["### `cache_ast_lake_nodes_ref`"]},"column":{"s":0,"e":30}},"dim":["","heading.74"],"code":"### `cache_ast_lake_nodes_ref`","symbName":"heading","symbRange":[9333,11715],"symbRangeL":[259,286],"outerCode":"\n**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.\n\n| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |\n\n**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):\n\n1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`\n2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`\n\n**`buildNodeRefId()`** (ast-lake.mjs:46-48):\n\n- For headings: returns last trail segment (e.g. `\"installation\"`)\n- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`\n\n**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.\n\n---","outerHtml":"\n<p><b>Role:</b> Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.</p>\n\n<p>| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | <b>Readable refId</b> — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |</p>\n\n<p><b>`buildNodeAbsoluteId()` construction</b> (ast-lake.mjs:29-44):</p>\n\n<ol><li>Strips `/repo/` prefix from fn → `relative/path/file.mdd`</li><li>Appends `::` + sanitized trail segments joined by `/`</li><li>For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor</li><li>Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`</li></ol>\n\n<p><b>`buildNodeRefId()`</b> (ast-lake.mjs:46-48):</p>\n\n<ul><li>For headings: returns last trail segment (e.g. `\"installation\"`)</li><li>For links: returns nomen (link label, e.g. `\"click here\"`)</li><li>This is the short ref stored in `cache_ast_lake_nodes.ref`</li></ul>\n\n<p><b>Why two ref tables?</b> `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.</p>\n\n<hr/>"},{"id":"/root/children/74/children/0","type":"inlineCode","loc":{"start":9305,"end":9331,"line":{"s":259,"e":259,"code":["### `cache_ast_lake_nodes_ref`"]},"column":{"s":4,"e":30}},"dim":["","heading.74","inlineCode.0"],"code":"`cache_ast_lake_nodes_ref`"},{"id":"/root/children/75","type":"paragraph","loc":{"start":9333,"end":9497,"line":{"s":261,"e":261,"code":["**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref."]},"column":{"s":0,"e":164}},"dim":["","paragraph.75"],"code":"**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref."},{"id":"/root/children/75/children/0","type":"strong","loc":{"start":9333,"end":9342,"line":{"s":261,"e":261,"code":["**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref."]},"column":{"s":0,"e":9}},"dim":["","paragraph.75","strong.0"],"code":"**Role:**"},{"id":"/root/children/75/children/0/children/0","type":"text","loc":{"start":9335,"end":9340,"line":{"s":261,"e":261,"code":["**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref."]},"column":{"s":2,"e":7}},"dim":["","paragraph.75","strong.0","text.0"],"code":"Role:"},{"id":"/root/children/75/children/1","type":"text","loc":{"start":9342,"end":9497,"line":{"s":261,"e":261,"code":["**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref."]},"column":{"s":9,"e":164}},"dim":["","paragraph.75","text.1"],"code":" Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref."},{"id":"/root/children/76","type":"paragraph","loc":{"start":9499,"end":10884,"line":{"s":263,"e":268,"code":["| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |","| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |","| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |","| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |","| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |","| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":0,"e":230}},"dim":["","paragraph.76"],"code":"| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"},{"id":"/root/children/76/children/0","type":"text","loc":{"start":9499,"end":9963,"line":{"s":263,"e":265,"code":["| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |","| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |","| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.76","text.0"],"code":"| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/76/children/1","type":"inlineCode","loc":{"start":9963,"end":9967,"line":{"s":265,"e":265,"code":["| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.76","inlineCode.1"],"code":"`id`"},{"id":"/root/children/76/children/2","type":"text","loc":{"start":9967,"end":9987,"line":{"s":265,"e":265,"code":["| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |"]},"column":{"s":6,"e":26}},"dim":["","paragraph.76","text.2"],"code":"   | Same as nodes: "},{"id":"/root/children/76/children/3","type":"inlineCode","loc":{"start":9987,"end":9998,"line":{"s":265,"e":265,"code":["| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |"]},"column":{"s":26,"e":37}},"dim":["","paragraph.76","inlineCode.3"],"code":"`fn:offset`"},{"id":"/root/children/76/children/4","type":"text","loc":{"start":9998,"end":10026,"line":{"s":265,"e":265,"code":["| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |"]},"column":{"s":37,"e":65}},"dim":["","paragraph.76","text.4"],"code":"                 | Joins to "},{"id":"/root/children/76/children/5","type":"inlineCode","loc":{"start":10026,"end":10051,"line":{"s":265,"e":265,"code":["| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |"]},"column":{"s":65,"e":90}},"dim":["","paragraph.76","inlineCode.5"],"code":"`cache_ast_lake_nodes.id`"},{"id":"/root/children/76/children/6","type":"text","loc":{"start":10051,"end":10194,"line":{"s":265,"e":266,"code":["| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |","| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |"]},"column":{"s":90,"e":2}},"dim":["","paragraph.76","text.6"],"code":"                                                                                                                                           |\n| "},{"id":"/root/children/76/children/7","type":"inlineCode","loc":{"start":10194,"end":10199,"line":{"s":266,"e":266,"code":["| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.76","inlineCode.7"],"code":"`sem`"},{"id":"/root/children/76/children/8","type":"text","loc":{"start":10199,"end":10203,"line":{"s":266,"e":266,"code":["| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |"]},"column":{"s":7,"e":11}},"dim":["","paragraph.76","text.8"],"code":"  | "},{"id":"/root/children/76/children/9","type":"inlineCode","loc":{"start":10203,"end":10214,"line":{"s":266,"e":266,"code":["| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |"]},"column":{"s":11,"e":22}},"dim":["","paragraph.76","inlineCode.9"],"code":"`node.type`"},{"id":"/root/children/76/children/10","type":"text","loc":{"start":10214,"end":10425,"line":{"s":266,"e":267,"code":["| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |","| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":22,"e":2}},"dim":["","paragraph.76","text.10"],"code":"                                | Node type (same as nodes.sem)                                                                                                                                                |\n| "},{"id":"/root/children/76/children/11","type":"inlineCode","loc":{"start":10425,"end":10430,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.76","inlineCode.11"],"code":"`ref`"},{"id":"/root/children/76/children/12","type":"text","loc":{"start":10430,"end":10434,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":7,"e":11}},"dim":["","paragraph.76","text.12"],"code":"  | "},{"id":"/root/children/76/children/13","type":"inlineCode","loc":{"start":10434,"end":10461,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":11,"e":38}},"dim":["","paragraph.76","inlineCode.13"],"code":"`buildNodeAbsoluteId(node)`"},{"id":"/root/children/76/children/14","type":"text","loc":{"start":10461,"end":10479,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":38,"e":56}},"dim":["","paragraph.76","text.14"],"code":"                | "},{"id":"/root/children/76/children/15","type":"strong","loc":{"start":10479,"end":10497,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":56,"e":74}},"dim":["","paragraph.76","strong.15"],"code":"**Readable refId**"},{"id":"/root/children/76/children/15/children/0","type":"text","loc":{"start":10481,"end":10495,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":58,"e":72}},"dim":["","paragraph.76","strong.15","text.0"],"code":"Readable refId"},{"id":"/root/children/76/children/16","type":"text","loc":{"start":10497,"end":10508,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":74,"e":85}},"dim":["","paragraph.76","text.16"],"code":" — format: "},{"id":"/root/children/76/children/17","type":"inlineCode","loc":{"start":10508,"end":10554,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":85,"e":131}},"dim":["","paragraph.76","inlineCode.17"],"code":"`relative/path/file.mdd::sanitized/trail/path`"},{"id":"/root/children/76/children/18","type":"text","loc":{"start":10554,"end":10567,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":131,"e":144}},"dim":["","paragraph.76","text.18"],"code":". For links: "},{"id":"/root/children/76/children/19","type":"inlineCode","loc":{"start":10567,"end":10599,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":144,"e":176}},"dim":["","paragraph.76","inlineCode.19"],"code":"`...::trail#nomen:tinyHash(url)`"},{"id":"/root/children/76/children/20","type":"text","loc":{"start":10599,"end":10610,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":176,"e":187}},"dim":["","paragraph.76","text.20"],"code":". Built by "},{"id":"/root/children/76/children/21","type":"inlineCode","loc":{"start":10610,"end":10633,"line":{"s":267,"e":267,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |"]},"column":{"s":187,"e":210}},"dim":["","paragraph.76","inlineCode.21"],"code":"`buildNodeAbsoluteId()`"},{"id":"/root/children/76/children/22","type":"text","loc":{"start":10633,"end":10656,"line":{"s":267,"e":268,"code":["| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |","| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":210,"e":2}},"dim":["","paragraph.76","text.22"],"code":" (ast-lake.mjs:29) |\n| "},{"id":"/root/children/76/children/23","type":"inlineCode","loc":{"start":10656,"end":10662,"line":{"s":268,"e":268,"code":["| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.76","inlineCode.23"],"code":"`body`"},{"id":"/root/children/76/children/24","type":"text","loc":{"start":10662,"end":10665,"line":{"s":268,"e":268,"code":["| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":8,"e":11}},"dim":["","paragraph.76","text.24"],"code":" | "},{"id":"/root/children/76/children/25","type":"inlineCode","loc":{"start":10665,"end":10674,"line":{"s":268,"e":268,"code":["| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":11,"e":20}},"dim":["","paragraph.76","inlineCode.25"],"code":"`linkUrl`"},{"id":"/root/children/76/children/26","type":"text","loc":{"start":10674,"end":10686,"line":{"s":268,"e":268,"code":["| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":20,"e":32}},"dim":["","paragraph.76","text.26"],"code":" for links, "},{"id":"/root/children/76/children/27","type":"inlineCode","loc":{"start":10686,"end":10696,"line":{"s":268,"e":268,"code":["| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":32,"e":42}},"dim":["","paragraph.76","inlineCode.27"],"code":"`repoName`"},{"id":"/root/children/76/children/28","type":"text","loc":{"start":10696,"end":10884,"line":{"s":268,"e":268,"code":["| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"]},"column":{"s":42,"e":230}},"dim":["","paragraph.76","text.28"],"code":" for others | For links: the URL target. For headings: the repo name.                                                                                                                      |"},{"id":"/root/children/77","type":"paragraph","loc":{"start":10886,"end":10948,"line":{"s":270,"e":270,"code":["**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):"]},"column":{"s":0,"e":62}},"dim":["","paragraph.77"],"code":"**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):"},{"id":"/root/children/77/children/0","type":"strong","loc":{"start":10886,"end":10926,"line":{"s":270,"e":270,"code":["**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):"]},"column":{"s":0,"e":40}},"dim":["","paragraph.77","strong.0"],"code":"**`buildNodeAbsoluteId()` construction**"},{"id":"/root/children/77/children/0/children/0","type":"inlineCode","loc":{"start":10888,"end":10911,"line":{"s":270,"e":270,"code":["**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):"]},"column":{"s":2,"e":25}},"dim":["","paragraph.77","strong.0","inlineCode.0"],"code":"`buildNodeAbsoluteId()`"},{"id":"/root/children/77/children/0/children/1","type":"text","loc":{"start":10911,"end":10924,"line":{"s":270,"e":270,"code":["**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):"]},"column":{"s":25,"e":38}},"dim":["","paragraph.77","strong.0","text.1"],"code":" construction"},{"id":"/root/children/77/children/1","type":"text","loc":{"start":10926,"end":10948,"line":{"s":270,"e":270,"code":["**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):"]},"column":{"s":40,"e":62}},"dim":["","paragraph.77","text.1"],"code":" (ast-lake.mjs:29-44):"},{"id":"/root/children/78","type":"list","loc":{"start":10950,"end":11215,"line":{"s":272,"e":275,"code":["1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`","2. Appends `::` + sanitized trail segments joined by `/`","3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor","4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"]},"column":{"s":0,"e":81}},"dim":["","list.78"],"code":"1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`\n2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`","symbName":"list","symbRange":[11217,11261],"symbRangeL":[272,278],"outerCode":"2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`\n\n**`buildNodeRefId()`** (ast-lake.mjs:46-48):","outerHtml":"<ol><li>Appends `::` + sanitized trail segments joined by `/`</li><li>For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor</li><li>Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`</li></ol>\n\n<p><b>`buildNodeRefId()`</b> (ast-lake.mjs:46-48):</p>"},{"id":"/root/children/78/children/0","type":"listItem","loc":{"start":10950,"end":11010,"line":{"s":272,"e":272,"code":["1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`"]},"column":{"s":0,"e":60}},"dim":["","list.78","listItem.0"],"code":"1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`"},{"id":"/root/children/78/children/0/children/0","type":"paragraph","loc":{"start":10953,"end":11010,"line":{"s":272,"e":272,"code":["1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`"]},"column":{"s":3,"e":60}},"dim":["","list.78","listItem.0","paragraph.0"],"code":"Strips `/repo/` prefix from fn → `relative/path/file.mdd`"},{"id":"/root/children/78/children/0/children/0/children/0","type":"text","loc":{"start":10953,"end":10960,"line":{"s":272,"e":272,"code":["1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`"]},"column":{"s":3,"e":10}},"dim":["","list.78","listItem.0","paragraph.0","text.0"],"code":"Strips "},{"id":"/root/children/78/children/0/children/0/children/1","type":"inlineCode","loc":{"start":10960,"end":10968,"line":{"s":272,"e":272,"code":["1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`"]},"column":{"s":10,"e":18}},"dim":["","list.78","listItem.0","paragraph.0","inlineCode.1"],"code":"`/repo/`"},{"id":"/root/children/78/children/0/children/0/children/2","type":"text","loc":{"start":10968,"end":10986,"line":{"s":272,"e":272,"code":["1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`"]},"column":{"s":18,"e":36}},"dim":["","list.78","listItem.0","paragraph.0","text.2"],"code":" prefix from fn → "},{"id":"/root/children/78/children/0/children/0/children/3","type":"inlineCode","loc":{"start":10986,"end":11010,"line":{"s":272,"e":272,"code":["1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`"]},"column":{"s":36,"e":60}},"dim":["","list.78","listItem.0","paragraph.0","inlineCode.3"],"code":"`relative/path/file.mdd`"},{"id":"/root/children/78/children/1","type":"listItem","loc":{"start":11011,"end":11067,"line":{"s":273,"e":273,"code":["2. Appends `::` + sanitized trail segments joined by `/`"]},"column":{"s":0,"e":56}},"dim":["","list.78","listItem.1"],"code":"2. Appends `::` + sanitized trail segments joined by `/`"},{"id":"/root/children/78/children/1/children/0","type":"paragraph","loc":{"start":11014,"end":11067,"line":{"s":273,"e":273,"code":["2. Appends `::` + sanitized trail segments joined by `/`"]},"column":{"s":3,"e":56}},"dim":["","list.78","listItem.1","paragraph.0"],"code":"Appends `::` + sanitized trail segments joined by `/`"},{"id":"/root/children/78/children/1/children/0/children/0","type":"text","loc":{"start":11014,"end":11022,"line":{"s":273,"e":273,"code":["2. Appends `::` + sanitized trail segments joined by `/`"]},"column":{"s":3,"e":11}},"dim":["","list.78","listItem.1","paragraph.0","text.0"],"code":"Appends "},{"id":"/root/children/78/children/1/children/0/children/1","type":"inlineCode","loc":{"start":11022,"end":11026,"line":{"s":273,"e":273,"code":["2. Appends `::` + sanitized trail segments joined by `/`"]},"column":{"s":11,"e":15}},"dim":["","list.78","listItem.1","paragraph.0","inlineCode.1"],"code":"`::`"},{"id":"/root/children/78/children/1/children/0/children/2","type":"text","loc":{"start":11026,"end":11064,"line":{"s":273,"e":273,"code":["2. Appends `::` + sanitized trail segments joined by `/`"]},"column":{"s":15,"e":53}},"dim":["","list.78","listItem.1","paragraph.0","text.2"],"code":" + sanitized trail segments joined by "},{"id":"/root/children/78/children/1/children/0/children/3","type":"inlineCode","loc":{"start":11064,"end":11067,"line":{"s":273,"e":273,"code":["2. Appends `::` + sanitized trail segments joined by `/`"]},"column":{"s":53,"e":56}},"dim":["","list.78","listItem.1","paragraph.0","inlineCode.3"],"code":"`/`"},{"id":"/root/children/78/children/2","type":"listItem","loc":{"start":11068,"end":11133,"line":{"s":274,"e":274,"code":["3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor"]},"column":{"s":0,"e":65}},"dim":["","list.78","listItem.2"],"code":"3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor"},{"id":"/root/children/78/children/2/children/0","type":"paragraph","loc":{"start":11071,"end":11133,"line":{"s":274,"e":274,"code":["3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor"]},"column":{"s":3,"e":65}},"dim":["","list.78","listItem.2","paragraph.0"],"code":"For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor"},{"id":"/root/children/78/children/2/children/0/children/0","type":"text","loc":{"start":11071,"end":11090,"line":{"s":274,"e":274,"code":["3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor"]},"column":{"s":3,"e":22}},"dim":["","list.78","listItem.2","paragraph.0","text.0"],"code":"For links: appends "},{"id":"/root/children/78/children/2/children/0/children/1","type":"inlineCode","loc":{"start":11090,"end":11126,"line":{"s":274,"e":274,"code":["3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor"]},"column":{"s":22,"e":58}},"dim":["","list.78","listItem.2","paragraph.0","inlineCode.1"],"code":"`#sanitizeName(nomen):tinyHash(url)`"},{"id":"/root/children/78/children/2/children/0/children/2","type":"text","loc":{"start":11126,"end":11133,"line":{"s":274,"e":274,"code":["3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor"]},"column":{"s":58,"e":65}},"dim":["","list.78","listItem.2","paragraph.0","text.2"],"code":" anchor"},{"id":"/root/children/78/children/3","type":"listItem","loc":{"start":11134,"end":11215,"line":{"s":275,"e":275,"code":["4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"]},"column":{"s":0,"e":81}},"dim":["","list.78","listItem.3"],"code":"4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"},{"id":"/root/children/78/children/3/children/0","type":"paragraph","loc":{"start":11137,"end":11215,"line":{"s":275,"e":275,"code":["4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"]},"column":{"s":3,"e":81}},"dim":["","list.78","listItem.3","paragraph.0"],"code":"Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"},{"id":"/root/children/78/children/3/children/0/children/0","type":"text","loc":{"start":11137,"end":11153,"line":{"s":275,"e":275,"code":["4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"]},"column":{"s":3,"e":19}},"dim":["","list.78","listItem.3","paragraph.0","text.0"],"code":"Result example: "},{"id":"/root/children/78/children/3/children/0/children/1","type":"inlineCode","loc":{"start":11153,"end":11215,"line":{"s":275,"e":275,"code":["4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"]},"column":{"s":19,"e":81}},"dim":["","list.78","listItem.3","paragraph.0","inlineCode.1"],"code":"`paper/visualization.mdd::visualization/graphviz#osage:ab12cd`"},{"id":"/root/children/79","type":"paragraph","loc":{"start":11217,"end":11261,"line":{"s":277,"e":277,"code":["**`buildNodeRefId()`** (ast-lake.mjs:46-48):"]},"column":{"s":0,"e":44}},"dim":["","paragraph.79"],"code":"**`buildNodeRefId()`** (ast-lake.mjs:46-48):"},{"id":"/root/children/79/children/0","type":"strong","loc":{"start":11217,"end":11239,"line":{"s":277,"e":277,"code":["**`buildNodeRefId()`** (ast-lake.mjs:46-48):"]},"column":{"s":0,"e":22}},"dim":["","paragraph.79","strong.0"],"code":"**`buildNodeRefId()`**"},{"id":"/root/children/79/children/0/children/0","type":"inlineCode","loc":{"start":11219,"end":11237,"line":{"s":277,"e":277,"code":["**`buildNodeRefId()`** (ast-lake.mjs:46-48):"]},"column":{"s":2,"e":20}},"dim":["","paragraph.79","strong.0","inlineCode.0"],"code":"`buildNodeRefId()`"},{"id":"/root/children/79/children/1","type":"text","loc":{"start":11239,"end":11261,"line":{"s":277,"e":277,"code":["**`buildNodeRefId()`** (ast-lake.mjs:46-48):"]},"column":{"s":22,"e":44}},"dim":["","paragraph.79","text.1"],"code":" (ast-lake.mjs:46-48):"},{"id":"/root/children/80","type":"list","loc":{"start":11263,"end":11451,"line":{"s":279,"e":281,"code":["- For headings: returns last trail segment (e.g. `\"installation\"`)","- For links: returns nomen (link label, e.g. `\"click here\"`)","- This is the short ref stored in `cache_ast_lake_nodes.ref`"]},"column":{"s":0,"e":60}},"dim":["","list.80"],"code":"- For headings: returns last trail segment (e.g. `\"installation\"`)\n- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`","symbName":"list","symbRange":[11453,16362],"symbRangeL":[279,373],"outerCode":"- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`\n\n**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.\n\n---\n\n### `cache_ast_lake_nodes_backup`\n\n**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):\n\n```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```\n\nThen the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.\n\nNote: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.\n\n---\n\n## `linetime`\n\n**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.\n\nSchema (commented inline in `ast-lake.mjs:122-131`):\n\n| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |\n\n**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):\n\n```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```\n\nThe result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.\n\nSee `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.\n\n---\n\n## `repo_voting`\n\n**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.\n\n| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |\n\n**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`.\n\n**Key query patterns:**\n\nDashboard voting (count per ref):\n\n```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```\n\nHeading-changes metadata check (`hasMetaData`):\n\n```sql\nSELECT DISTINCT ref FROM repo_voting\nWHERE ref IN ('ref1', 'ref2', ...)\n```\n\n**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`.\n\n**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):","outerHtml":"<ul><li>For links: returns nomen (link label, e.g. `\"click here\"`)</li><li>This is the short ref stored in `cache_ast_lake_nodes.ref`</li></ul>\n\n<p><b>Why two ref tables?</b> `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_backup`</h3>\n\n<p><b>Role:</b> Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):</p>\n\n<p>```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```</p>\n\n<p>Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.</p>\n\n<p>Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.</p>\n\n<hr/>\n\n<h2>`linetime`</h2>\n\n<p><b>Role:</b> Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.</p>\n\n<p>Schema (commented inline in `ast-lake.mjs:122-131`):</p>\n\n<p>| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`, ast-lake.mjs:473):</p>\n\n<p>```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```</p>\n\n<p>The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.</p>\n\n<p>See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.</p>\n\n<hr/>\n\n<h2>`repo_voting`</h2>\n\n<p><b>Role:</b> Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.</p>\n\n<p>| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |</p>\n\n<p><b>Indexes:</b> `num1`, `num2`, `campaign`, `operation`, `ref`.</p>\n\n<p><b>Key query patterns:</b></p>\n\n<p>Dashboard voting (count per ref):</p>\n\n<p>```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```</p>\n\n<p>Heading-changes metadata check (`hasMetaData`):</p>\n\n<p>```sql\nSELECT DISTINCT ref FROM repo_voting\nWHERE ref IN ('ref1', 'ref2', ...)\n```</p>\n\n<p><b>ref format:</b> Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`.</p>\n\n<p><b>Cleanup on file deletion</b> (ast-lake.mjs `cleanup()` at line 305):</p>"},{"id":"/root/children/80/children/0","type":"listItem","loc":{"start":11263,"end":11329,"line":{"s":279,"e":279,"code":["- For headings: returns last trail segment (e.g. `\"installation\"`)"]},"column":{"s":0,"e":66}},"dim":["","list.80","listItem.0"],"code":"- For headings: returns last trail segment (e.g. `\"installation\"`)"},{"id":"/root/children/80/children/0/children/0","type":"paragraph","loc":{"start":11265,"end":11329,"line":{"s":279,"e":279,"code":["- For headings: returns last trail segment (e.g. `\"installation\"`)"]},"column":{"s":2,"e":66}},"dim":["","list.80","listItem.0","paragraph.0"],"code":"For headings: returns last trail segment (e.g. `\"installation\"`)"},{"id":"/root/children/80/children/0/children/0/children/0","type":"text","loc":{"start":11265,"end":11312,"line":{"s":279,"e":279,"code":["- For headings: returns last trail segment (e.g. `\"installation\"`)"]},"column":{"s":2,"e":49}},"dim":["","list.80","listItem.0","paragraph.0","text.0"],"code":"For headings: returns last trail segment (e.g. "},{"id":"/root/children/80/children/0/children/0/children/1","type":"inlineCode","loc":{"start":11312,"end":11328,"line":{"s":279,"e":279,"code":["- For headings: returns last trail segment (e.g. `\"installation\"`)"]},"column":{"s":49,"e":65}},"dim":["","list.80","listItem.0","paragraph.0","inlineCode.1"],"code":"`\"installation\"`"},{"id":"/root/children/80/children/0/children/0/children/2","type":"text","loc":{"start":11328,"end":11329,"line":{"s":279,"e":279,"code":["- For headings: returns last trail segment (e.g. `\"installation\"`)"]},"column":{"s":65,"e":66}},"dim":["","list.80","listItem.0","paragraph.0","text.2"],"code":")"},{"id":"/root/children/80/children/1","type":"listItem","loc":{"start":11330,"end":11390,"line":{"s":280,"e":280,"code":["- For links: returns nomen (link label, e.g. `\"click here\"`)"]},"column":{"s":0,"e":60}},"dim":["","list.80","listItem.1"],"code":"- For links: returns nomen (link label, e.g. `\"click here\"`)"},{"id":"/root/children/80/children/1/children/0","type":"paragraph","loc":{"start":11332,"end":11390,"line":{"s":280,"e":280,"code":["- For links: returns nomen (link label, e.g. `\"click here\"`)"]},"column":{"s":2,"e":60}},"dim":["","list.80","listItem.1","paragraph.0"],"code":"For links: returns nomen (link label, e.g. `\"click here\"`)"},{"id":"/root/children/80/children/1/children/0/children/0","type":"text","loc":{"start":11332,"end":11375,"line":{"s":280,"e":280,"code":["- For links: returns nomen (link label, e.g. `\"click here\"`)"]},"column":{"s":2,"e":45}},"dim":["","list.80","listItem.1","paragraph.0","text.0"],"code":"For links: returns nomen (link label, e.g. "},{"id":"/root/children/80/children/1/children/0/children/1","type":"inlineCode","loc":{"start":11375,"end":11389,"line":{"s":280,"e":280,"code":["- For links: returns nomen (link label, e.g. `\"click here\"`)"]},"column":{"s":45,"e":59}},"dim":["","list.80","listItem.1","paragraph.0","inlineCode.1"],"code":"`\"click here\"`"},{"id":"/root/children/80/children/1/children/0/children/2","type":"text","loc":{"start":11389,"end":11390,"line":{"s":280,"e":280,"code":["- For links: returns nomen (link label, e.g. `\"click here\"`)"]},"column":{"s":59,"e":60}},"dim":["","list.80","listItem.1","paragraph.0","text.2"],"code":")"},{"id":"/root/children/80/children/2","type":"listItem","loc":{"start":11391,"end":11451,"line":{"s":281,"e":281,"code":["- This is the short ref stored in `cache_ast_lake_nodes.ref`"]},"column":{"s":0,"e":60}},"dim":["","list.80","listItem.2"],"code":"- This is the short ref stored in `cache_ast_lake_nodes.ref`"},{"id":"/root/children/80/children/2/children/0","type":"paragraph","loc":{"start":11393,"end":11451,"line":{"s":281,"e":281,"code":["- This is the short ref stored in `cache_ast_lake_nodes.ref`"]},"column":{"s":2,"e":60}},"dim":["","list.80","listItem.2","paragraph.0"],"code":"This is the short ref stored in `cache_ast_lake_nodes.ref`"},{"id":"/root/children/80/children/2/children/0/children/0","type":"text","loc":{"start":11393,"end":11425,"line":{"s":281,"e":281,"code":["- This is the short ref stored in `cache_ast_lake_nodes.ref`"]},"column":{"s":2,"e":34}},"dim":["","list.80","listItem.2","paragraph.0","text.0"],"code":"This is the short ref stored in "},{"id":"/root/children/80/children/2/children/0/children/1","type":"inlineCode","loc":{"start":11425,"end":11451,"line":{"s":281,"e":281,"code":["- This is the short ref stored in `cache_ast_lake_nodes.ref`"]},"column":{"s":34,"e":60}},"dim":["","list.80","listItem.2","paragraph.0","inlineCode.1"],"code":"`cache_ast_lake_nodes.ref`"},{"id":"/root/children/81","type":"paragraph","loc":{"start":11453,"end":11710,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":0,"e":257}},"dim":["","paragraph.81"],"code":"**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."},{"id":"/root/children/81/children/0","type":"strong","loc":{"start":11453,"end":11476,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":0,"e":23}},"dim":["","paragraph.81","strong.0"],"code":"**Why two ref tables?**"},{"id":"/root/children/81/children/0/children/0","type":"text","loc":{"start":11455,"end":11474,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":2,"e":21}},"dim":["","paragraph.81","strong.0","text.0"],"code":"Why two ref tables?"},{"id":"/root/children/81/children/1","type":"text","loc":{"start":11476,"end":11477,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":23,"e":24}},"dim":["","paragraph.81","text.1"],"code":" "},{"id":"/root/children/81/children/2","type":"inlineCode","loc":{"start":11477,"end":11503,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":24,"e":50}},"dim":["","paragraph.81","inlineCode.2"],"code":"`cache_ast_lake_nodes.ref`"},{"id":"/root/children/81/children/3","type":"text","loc":{"start":11503,"end":11554,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":50,"e":101}},"dim":["","paragraph.81","text.3"],"code":" has the short, per-node ref (last trail segment). "},{"id":"/root/children/81/children/4","type":"inlineCode","loc":{"start":11554,"end":11584,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":101,"e":131}},"dim":["","paragraph.81","inlineCode.4"],"code":"`cache_ast_lake_nodes_ref.ref`"},{"id":"/root/children/81/children/5","type":"text","loc":{"start":11584,"end":11698,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":131,"e":245}},"dim":["","paragraph.81","text.5"],"code":" has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from "},{"id":"/root/children/81/children/6","type":"inlineCode","loc":{"start":11698,"end":11709,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":245,"e":256}},"dim":["","paragraph.81","inlineCode.6"],"code":"`nodes_ref`"},{"id":"/root/children/81/children/7","type":"text","loc":{"start":11709,"end":11710,"line":{"s":283,"e":283,"code":["**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`."]},"column":{"s":256,"e":257}},"dim":["","paragraph.81","text.7"],"code":"."},{"id":"/root/children/82","type":"thematicBreak","loc":{"start":11712,"end":11715,"line":{"s":285,"e":285,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.82"],"code":"---"},{"id":"/root/children/83","type":"heading","loc":{"start":11717,"end":11750,"line":{"s":287,"e":287,"code":["### `cache_ast_lake_nodes_backup`"]},"column":{"s":0,"e":33}},"dim":["","heading.83"],"code":"### `cache_ast_lake_nodes_backup`","symbName":"heading","symbRange":[11752,12403],"symbRangeL":[287,304],"outerCode":"\n**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):\n\n```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```\n\nThen the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.\n\nNote: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.\n\n---","outerHtml":"\n<p><b>Role:</b> Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):</p>\n\n<p>```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```</p>\n\n<p>Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.</p>\n\n<p>Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.</p>\n\n<hr/>"},{"id":"/root/children/83/children/0","type":"inlineCode","loc":{"start":11721,"end":11750,"line":{"s":287,"e":287,"code":["### `cache_ast_lake_nodes_backup`"]},"column":{"s":4,"e":33}},"dim":["","heading.83","inlineCode.0"],"code":"`cache_ast_lake_nodes_backup`"},{"id":"/root/children/84","type":"paragraph","loc":{"start":11752,"end":11887,"line":{"s":289,"e":289,"code":["**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):"]},"column":{"s":0,"e":135}},"dim":["","paragraph.84"],"code":"**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):"},{"id":"/root/children/84/children/0","type":"strong","loc":{"start":11752,"end":11761,"line":{"s":289,"e":289,"code":["**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):"]},"column":{"s":0,"e":9}},"dim":["","paragraph.84","strong.0"],"code":"**Role:**"},{"id":"/root/children/84/children/0/children/0","type":"text","loc":{"start":11754,"end":11759,"line":{"s":289,"e":289,"code":["**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):"]},"column":{"s":2,"e":7}},"dim":["","paragraph.84","strong.0","text.0"],"code":"Role:"},{"id":"/root/children/84/children/1","type":"text","loc":{"start":11761,"end":11811,"line":{"s":289,"e":289,"code":["**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):"]},"column":{"s":9,"e":59}},"dim":["","paragraph.84","text.1"],"code":" Rollback copy. Before updating a file's nodes in "},{"id":"/root/children/84/children/2","type":"inlineCode","loc":{"start":11811,"end":11833,"line":{"s":289,"e":289,"code":["**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):"]},"column":{"s":59,"e":81}},"dim":["","paragraph.84","inlineCode.2"],"code":"`cache_ast_lake_nodes`"},{"id":"/root/children/84/children/3","type":"text","loc":{"start":11833,"end":11887,"line":{"s":289,"e":289,"code":["**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):"]},"column":{"s":81,"e":135}},"dim":["","paragraph.84","text.3"],"code":", the old rows are copied here (ast-lake.mjs:500-514):"},{"id":"/root/children/85","type":"code","loc":{"start":11889,"end":12101,"line":{"s":291,"e":297,"code":["```sql","INSERT OR REPLACE INTO cache_ast_lake_nodes_backup","  (id, value, mt, sem, num1, num2, ref, body)","SELECT id, value, mt, sem, num1, num2, ref, body","FROM cache_ast_lake_nodes","WHERE id LIKE '<escapedFn>:%'","```"]},"column":{"s":0,"e":3}},"dim":["","code.85"],"code":"```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```","symbName":"code","symbRange":[12103,13521],"symbRangeL":[null,322],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):\n\n- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):\n\n```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```\n\n---\n\n### `cache_ast_lake_nodes_ref`\n\n**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.\n\n| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |\n\n**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):\n\n1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`\n2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`\n\n**`buildNodeRefId()`** (ast-lake.mjs:46-48):\n\n- For headings: returns last trail segment (e.g. `\"installation\"`)\n- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`\n\n**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.\n\n---\n\n### `cache_ast_lake_nodes_backup`\n\n**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):\n\n```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```\n\nThen the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.\n\nNote: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.\n\n---\n\n## `linetime`\n\n**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.\n\nSchema (commented inline in `ast-lake.mjs:122-131`):\n\n| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |\n\n**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>\n\n<ul><li><b>headings</b>: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops</li><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>\n\n<p>```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_ref`</h3>\n\n<p><b>Role:</b> Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.</p>\n\n<p>| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | <b>Readable refId</b> — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |</p>\n\n<p><b>`buildNodeAbsoluteId()` construction</b> (ast-lake.mjs:29-44):</p>\n\n<ol><li>Strips `/repo/` prefix from fn → `relative/path/file.mdd`</li><li>Appends `::` + sanitized trail segments joined by `/`</li><li>For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor</li><li>Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`</li></ol>\n\n<p><b>`buildNodeRefId()`</b> (ast-lake.mjs:46-48):</p>\n\n<ul><li>For headings: returns last trail segment (e.g. `\"installation\"`)</li><li>For links: returns nomen (link label, e.g. `\"click here\"`)</li><li>This is the short ref stored in `cache_ast_lake_nodes.ref`</li></ul>\n\n<p><b>Why two ref tables?</b> `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_backup`</h3>\n\n<p><b>Role:</b> Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):</p>\n\n<p>```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```</p>\n\n<p>Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.</p>\n\n<p>Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.</p>\n\n<hr/>\n\n<h2>`linetime`</h2>\n\n<p><b>Role:</b> Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.</p>\n\n<p>Schema (commented inline in `ast-lake.mjs:122-131`):</p>\n\n<p>| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`, ast-lake.mjs:473):</p>"},{"id":"/root/children/86","type":"paragraph","loc":{"start":12103,"end":12284,"line":{"s":299,"e":299,"code":["Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored."]},"column":{"s":0,"e":181}},"dim":["","paragraph.86"],"code":"Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored."},{"id":"/root/children/86/children/0","type":"text","loc":{"start":12103,"end":12143,"line":{"s":299,"e":299,"code":["Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored."]},"column":{"s":0,"e":40}},"dim":["","paragraph.86","text.0"],"code":"Then the old rows are deleted from both "},{"id":"/root/children/86/children/1","type":"inlineCode","loc":{"start":12143,"end":12165,"line":{"s":299,"e":299,"code":["Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored."]},"column":{"s":40,"e":62}},"dim":["","paragraph.86","inlineCode.1"],"code":"`cache_ast_lake_nodes`"},{"id":"/root/children/86/children/2","type":"text","loc":{"start":12165,"end":12170,"line":{"s":299,"e":299,"code":["Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored."]},"column":{"s":62,"e":67}},"dim":["","paragraph.86","text.2"],"code":" and "},{"id":"/root/children/86/children/3","type":"inlineCode","loc":{"start":12170,"end":12196,"line":{"s":299,"e":299,"code":["Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored."]},"column":{"s":67,"e":93}},"dim":["","paragraph.86","inlineCode.3"],"code":"`cache_ast_lake_nodes_ref`"},{"id":"/root/children/86/children/4","type":"text","loc":{"start":12196,"end":12284,"line":{"s":299,"e":299,"code":["Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored."]},"column":{"s":93,"e":181}},"dim":["","paragraph.86","text.4"],"code":" before inserting the new normalized nodes. If a sync fails, the backup can be restored."},{"id":"/root/children/87","type":"paragraph","loc":{"start":12286,"end":12398,"line":{"s":301,"e":301,"code":["Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time."]},"column":{"s":0,"e":112}},"dim":["","paragraph.87"],"code":"Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time."},{"id":"/root/children/87/children/0","type":"text","loc":{"start":12286,"end":12292,"line":{"s":301,"e":301,"code":["Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time."]},"column":{"s":0,"e":6}},"dim":["","paragraph.87","text.0"],"code":"Note: "},{"id":"/root/children/87/children/1","type":"inlineCode","loc":{"start":12292,"end":12318,"line":{"s":301,"e":301,"code":["Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time."]},"column":{"s":6,"e":32}},"dim":["","paragraph.87","inlineCode.1"],"code":"`cache_ast_lake_nodes_ref`"},{"id":"/root/children/87/children/2","type":"text","loc":{"start":12318,"end":12398,"line":{"s":301,"e":301,"code":["Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time."]},"column":{"s":32,"e":112}},"dim":["","paragraph.87","text.2"],"code":" does NOT have a backup table — the ref table is rebuilt from scratch each time."},{"id":"/root/children/88","type":"thematicBreak","loc":{"start":12400,"end":12403,"line":{"s":303,"e":303,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.88"],"code":"---"},{"id":"/root/children/89","type":"heading","loc":{"start":12405,"end":12418,"line":{"s":305,"e":305,"code":["## `linetime`"]},"column":{"s":0,"e":13}},"dim":["","heading.89"],"code":"## `linetime`","symbName":"heading","symbRange":[12420,13888],"symbRangeL":[305,333],"outerCode":"\n**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.\n\nSchema (commented inline in `ast-lake.mjs:122-131`):\n\n| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |\n\n**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):\n\n```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```\n\nThe result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.\n\nSee `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.\n\n---","outerHtml":"\n<p><b>Role:</b> Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.</p>\n\n<p>Schema (commented inline in `ast-lake.mjs:122-131`):</p>\n\n<p>| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`, ast-lake.mjs:473):</p>\n\n<p>```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```</p>\n\n<p>The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.</p>\n\n<p>See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.</p>\n\n<hr/>"},{"id":"/root/children/89/children/0","type":"inlineCode","loc":{"start":12408,"end":12418,"line":{"s":305,"e":305,"code":["## `linetime`"]},"column":{"s":3,"e":13}},"dim":["","heading.89","inlineCode.0"],"code":"`linetime`"},{"id":"/root/children/90","type":"paragraph","loc":{"start":12420,"end":12630,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":0,"e":210}},"dim":["","paragraph.90"],"code":"**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."},{"id":"/root/children/90/children/0","type":"strong","loc":{"start":12420,"end":12429,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":0,"e":9}},"dim":["","paragraph.90","strong.0"],"code":"**Role:**"},{"id":"/root/children/90/children/0/children/0","type":"text","loc":{"start":12422,"end":12427,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":2,"e":7}},"dim":["","paragraph.90","strong.0","text.0"],"code":"Role:"},{"id":"/root/children/90/children/1","type":"text","loc":{"start":12429,"end":12556,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":9,"e":136}},"dim":["","paragraph.90","text.1"],"code":" Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by "},{"id":"/root/children/90/children/2","type":"inlineCode","loc":{"start":12556,"end":12574,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":136,"e":154}},"dim":["","paragraph.90","inlineCode.2"],"code":"`normalizeNodes()`"},{"id":"/root/children/90/children/3","type":"text","loc":{"start":12574,"end":12585,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":154,"e":165}},"dim":["","paragraph.90","text.3"],"code":" to attach "},{"id":"/root/children/90/children/4","type":"inlineCode","loc":{"start":12585,"end":12596,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":165,"e":176}},"dim":["","paragraph.90","inlineCode.4"],"code":"`symbBlame`"},{"id":"/root/children/90/children/5","type":"text","loc":{"start":12596,"end":12597,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":176,"e":177}},"dim":["","paragraph.90","text.5"],"code":"/"},{"id":"/root/children/90/children/6","type":"inlineCode","loc":{"start":12597,"end":12605,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":177,"e":185}},"dim":["","paragraph.90","inlineCode.6"],"code":"`symbMt`"},{"id":"/root/children/90/children/7","type":"text","loc":{"start":12605,"end":12606,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":185,"e":186}},"dim":["","paragraph.90","text.7"],"code":"/"},{"id":"/root/children/90/children/8","type":"inlineCode","loc":{"start":12606,"end":12616,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":186,"e":196}},"dim":["","paragraph.90","inlineCode.8"],"code":"`symbUser`"},{"id":"/root/children/90/children/9","type":"text","loc":{"start":12616,"end":12630,"line":{"s":307,"e":307,"code":["**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node."]},"column":{"s":196,"e":210}},"dim":["","paragraph.90","text.9"],"code":" to each node."},{"id":"/root/children/91","type":"paragraph","loc":{"start":12632,"end":12684,"line":{"s":309,"e":309,"code":["Schema (commented inline in `ast-lake.mjs:122-131`):"]},"column":{"s":0,"e":52}},"dim":["","paragraph.91"],"code":"Schema (commented inline in `ast-lake.mjs:122-131`):"},{"id":"/root/children/91/children/0","type":"text","loc":{"start":12632,"end":12660,"line":{"s":309,"e":309,"code":["Schema (commented inline in `ast-lake.mjs:122-131`):"]},"column":{"s":0,"e":28}},"dim":["","paragraph.91","text.0"],"code":"Schema (commented inline in "},{"id":"/root/children/91/children/1","type":"inlineCode","loc":{"start":12660,"end":12682,"line":{"s":309,"e":309,"code":["Schema (commented inline in `ast-lake.mjs:122-131`):"]},"column":{"s":28,"e":50}},"dim":["","paragraph.91","inlineCode.1"],"code":"`ast-lake.mjs:122-131`"},{"id":"/root/children/91/children/2","type":"text","loc":{"start":12682,"end":12684,"line":{"s":309,"e":309,"code":["Schema (commented inline in `ast-lake.mjs:122-131`):"]},"column":{"s":50,"e":52}},"dim":["","paragraph.91","text.2"],"code":"):"},{"id":"/root/children/92","type":"paragraph","loc":{"start":12686,"end":13459,"line":{"s":311,"e":319,"code":["| Column | Type             | Description                                           |","| ------ | ---------------- | ----------------------------------------------------- |","| `id`   | INTEGER          | Primary key, autoincrement                            |","| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |","| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |","| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |","| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |","| `user` | TEXT             | Author name or email                                  |","| `json` | TEXT             | Additional metadata as JSON blob                      |"]},"column":{"s":0,"e":85}},"dim":["","paragraph.92"],"code":"| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |"},{"id":"/root/children/92/children/0","type":"text","loc":{"start":12686,"end":12860,"line":{"s":311,"e":313,"code":["| Column | Type             | Description                                           |","| ------ | ---------------- | ----------------------------------------------------- |","| `id`   | INTEGER          | Primary key, autoincrement                            |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.92","text.0"],"code":"| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| "},{"id":"/root/children/92/children/1","type":"inlineCode","loc":{"start":12860,"end":12864,"line":{"s":313,"e":313,"code":["| `id`   | INTEGER          | Primary key, autoincrement                            |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.92","inlineCode.1"],"code":"`id`"},{"id":"/root/children/92/children/2","type":"text","loc":{"start":12864,"end":12946,"line":{"s":313,"e":314,"code":["| `id`   | INTEGER          | Primary key, autoincrement                            |","| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |"]},"column":{"s":6,"e":2}},"dim":["","paragraph.92","text.2"],"code":"   | INTEGER          | Primary key, autoincrement                            |\n| "},{"id":"/root/children/92/children/3","type":"inlineCode","loc":{"start":12946,"end":12950,"line":{"s":314,"e":314,"code":["| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.92","inlineCode.3"],"code":"`fn`"},{"id":"/root/children/92/children/4","type":"text","loc":{"start":12950,"end":13032,"line":{"s":314,"e":315,"code":["| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |","| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |"]},"column":{"s":6,"e":2}},"dim":["","paragraph.92","text.4"],"code":"   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| "},{"id":"/root/children/92/children/5","type":"inlineCode","loc":{"start":13032,"end":13038,"line":{"s":315,"e":315,"code":["| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.92","inlineCode.5"],"code":"`line`"},{"id":"/root/children/92/children/6","type":"text","loc":{"start":13038,"end":13118,"line":{"s":315,"e":316,"code":["| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |","| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.92","text.6"],"code":" | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| "},{"id":"/root/children/92/children/7","type":"inlineCode","loc":{"start":13118,"end":13124,"line":{"s":316,"e":316,"code":["| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.92","inlineCode.7"],"code":"`hash`"},{"id":"/root/children/92/children/8","type":"text","loc":{"start":13124,"end":13204,"line":{"s":316,"e":317,"code":["| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |","| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.92","text.8"],"code":" | TEXT NOT NULL    | Git commit hash from blame                            |\n| "},{"id":"/root/children/92/children/9","type":"inlineCode","loc":{"start":13204,"end":13208,"line":{"s":317,"e":317,"code":["| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.92","inlineCode.9"],"code":"`mt`"},{"id":"/root/children/92/children/10","type":"text","loc":{"start":13208,"end":13290,"line":{"s":317,"e":318,"code":["| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |","| `user` | TEXT             | Author name or email                                  |"]},"column":{"s":6,"e":2}},"dim":["","paragraph.92","text.10"],"code":"   | TEXT NOT NULL    | Commit author timestamp                               |\n| "},{"id":"/root/children/92/children/11","type":"inlineCode","loc":{"start":13290,"end":13296,"line":{"s":318,"e":318,"code":["| `user` | TEXT             | Author name or email                                  |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.92","inlineCode.11"],"code":"`user`"},{"id":"/root/children/92/children/12","type":"text","loc":{"start":13296,"end":13376,"line":{"s":318,"e":319,"code":["| `user` | TEXT             | Author name or email                                  |","| `json` | TEXT             | Additional metadata as JSON blob                      |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.92","text.12"],"code":" | TEXT             | Author name or email                                  |\n| "},{"id":"/root/children/92/children/13","type":"inlineCode","loc":{"start":13376,"end":13382,"line":{"s":319,"e":319,"code":["| `json` | TEXT             | Additional metadata as JSON blob                      |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.92","inlineCode.13"],"code":"`json`"},{"id":"/root/children/92/children/14","type":"text","loc":{"start":13382,"end":13459,"line":{"s":319,"e":319,"code":["| `json` | TEXT             | Additional metadata as JSON blob                      |"]},"column":{"s":8,"e":85}},"dim":["","paragraph.92","text.14"],"code":" | TEXT             | Additional metadata as JSON blob                      |"},{"id":"/root/children/93","type":"paragraph","loc":{"start":13461,"end":13521,"line":{"s":321,"e":321,"code":["**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):"]},"column":{"s":0,"e":60}},"dim":["","paragraph.93"],"code":"**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):"},{"id":"/root/children/93/children/0","type":"strong","loc":{"start":13461,"end":13478,"line":{"s":321,"e":321,"code":["**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):"]},"column":{"s":0,"e":17}},"dim":["","paragraph.93","strong.0"],"code":"**Query pattern**"},{"id":"/root/children/93/children/0/children/0","type":"text","loc":{"start":13463,"end":13476,"line":{"s":321,"e":321,"code":["**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):"]},"column":{"s":2,"e":15}},"dim":["","paragraph.93","strong.0","text.0"],"code":"Query pattern"},{"id":"/root/children/93/children/1","type":"text","loc":{"start":13478,"end":13483,"line":{"s":321,"e":321,"code":["**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):"]},"column":{"s":17,"e":22}},"dim":["","paragraph.93","text.1"],"code":" (in "},{"id":"/root/children/93/children/2","type":"inlineCode","loc":{"start":13483,"end":13501,"line":{"s":321,"e":321,"code":["**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):"]},"column":{"s":22,"e":40}},"dim":["","paragraph.93","inlineCode.2"],"code":"`actualizeNodes()`"},{"id":"/root/children/93/children/3","type":"text","loc":{"start":13501,"end":13521,"line":{"s":321,"e":321,"code":["**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):"]},"column":{"s":40,"e":60}},"dim":["","paragraph.93","text.3"],"code":", ast-lake.mjs:473):"},{"id":"/root/children/94","type":"code","loc":{"start":13523,"end":13624,"line":{"s":323,"e":326,"code":["```sql","SELECT line, mt, user, hash FROM linetime","WHERE fn='<filepath>' AND hash!=\"\" ORDER BY line","```"]},"column":{"s":0,"e":3}},"dim":["","code.94"],"code":"```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```","symbName":"code","symbRange":[13626,15804],"symbRangeL":[null,355],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):\n\n- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):\n\n```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```\n\n---\n\n### `cache_ast_lake_nodes_ref`\n\n**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.\n\n| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |\n\n**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):\n\n1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`\n2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`\n\n**`buildNodeRefId()`** (ast-lake.mjs:46-48):\n\n- For headings: returns last trail segment (e.g. `\"installation\"`)\n- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`\n\n**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.\n\n---\n\n### `cache_ast_lake_nodes_backup`\n\n**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):\n\n```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```\n\nThen the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.\n\nNote: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.\n\n---\n\n## `linetime`\n\n**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.\n\nSchema (commented inline in `ast-lake.mjs:122-131`):\n\n| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |\n\n**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):\n\n```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```\n\nThe result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.\n\nSee `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.\n\n---\n\n## `repo_voting`\n\n**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.\n\n| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |\n\n**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`.\n\n**Key query patterns:**\n\nDashboard voting (count per ref):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>\n\n<ul><li><b>headings</b>: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops</li><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>\n\n<p>```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_ref`</h3>\n\n<p><b>Role:</b> Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.</p>\n\n<p>| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | <b>Readable refId</b> — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |</p>\n\n<p><b>`buildNodeAbsoluteId()` construction</b> (ast-lake.mjs:29-44):</p>\n\n<ol><li>Strips `/repo/` prefix from fn → `relative/path/file.mdd`</li><li>Appends `::` + sanitized trail segments joined by `/`</li><li>For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor</li><li>Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`</li></ol>\n\n<p><b>`buildNodeRefId()`</b> (ast-lake.mjs:46-48):</p>\n\n<ul><li>For headings: returns last trail segment (e.g. `\"installation\"`)</li><li>For links: returns nomen (link label, e.g. `\"click here\"`)</li><li>This is the short ref stored in `cache_ast_lake_nodes.ref`</li></ul>\n\n<p><b>Why two ref tables?</b> `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_backup`</h3>\n\n<p><b>Role:</b> Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):</p>\n\n<p>```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```</p>\n\n<p>Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.</p>\n\n<p>Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.</p>\n\n<hr/>\n\n<h2>`linetime`</h2>\n\n<p><b>Role:</b> Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.</p>\n\n<p>Schema (commented inline in `ast-lake.mjs:122-131`):</p>\n\n<p>| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`, ast-lake.mjs:473):</p>\n\n<p>```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```</p>\n\n<p>The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.</p>\n\n<p>See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.</p>\n\n<hr/>\n\n<h2>`repo_voting`</h2>\n\n<p><b>Role:</b> Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.</p>\n\n<p>| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |</p>\n\n<p><b>Indexes:</b> `num1`, `num2`, `campaign`, `operation`, `ref`.</p>\n\n<p><b>Key query patterns:</b></p>\n\n<p>Dashboard voting (count per ref):</p>"},{"id":"/root/children/95","type":"paragraph","loc":{"start":13626,"end":13757,"line":{"s":328,"e":328,"code":["The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range."]},"column":{"s":0,"e":131}},"dim":["","paragraph.95"],"code":"The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range."},{"id":"/root/children/95/children/0","type":"text","loc":{"start":13626,"end":13656,"line":{"s":328,"e":328,"code":["The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range."]},"column":{"s":0,"e":30}},"dim":["","paragraph.95","text.0"],"code":"The result is used to compute "},{"id":"/root/children/95/children/1","type":"inlineCode","loc":{"start":13656,"end":13668,"line":{"s":328,"e":328,"code":["The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range."]},"column":{"s":30,"e":42}},"dim":["","paragraph.95","inlineCode.1"],"code":"`symbRangeL`"},{"id":"/root/children/95/children/2","type":"text","loc":{"start":13668,"end":13671,"line":{"s":328,"e":328,"code":["The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range."]},"column":{"s":42,"e":45}},"dim":["","paragraph.95","text.2"],"code":" → "},{"id":"/root/children/95/children/3","type":"inlineCode","loc":{"start":13671,"end":13682,"line":{"s":328,"e":328,"code":["The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range."]},"column":{"s":45,"e":56}},"dim":["","paragraph.95","inlineCode.3"],"code":"`symbBlame`"},{"id":"/root/children/95/children/4","type":"text","loc":{"start":13682,"end":13757,"line":{"s":328,"e":328,"code":["The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range."]},"column":{"s":56,"e":131}},"dim":["","paragraph.95","text.4"],"code":" for each node: all commit hashes for lines within the node's source range."},{"id":"/root/children/96","type":"paragraph","loc":{"start":13759,"end":13883,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":0,"e":124}},"dim":["","paragraph.96"],"code":"See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."},{"id":"/root/children/96/children/0","type":"text","loc":{"start":13759,"end":13763,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":0,"e":4}},"dim":["","paragraph.96","text.0"],"code":"See "},{"id":"/root/children/96/children/1","type":"inlineCode","loc":{"start":13763,"end":13788,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":4,"e":29}},"dim":["","paragraph.96","inlineCode.1"],"code":"`linetime-parse-blame.jq`"},{"id":"/root/children/96/children/2","type":"text","loc":{"start":13788,"end":13826,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":29,"e":67}},"dim":["","paragraph.96","text.2"],"code":" for the processing logic that parses "},{"id":"/root/children/96/children/3","type":"inlineCode","loc":{"start":13826,"end":13854,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":67,"e":95}},"dim":["","paragraph.96","inlineCode.3"],"code":"`git blame --line-porcelain`"},{"id":"/root/children/96/children/4","type":"text","loc":{"start":13854,"end":13867,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":95,"e":108}},"dim":["","paragraph.96","text.4"],"code":" output into "},{"id":"/root/children/96/children/5","type":"inlineCode","loc":{"start":13867,"end":13877,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":108,"e":118}},"dim":["","paragraph.96","inlineCode.5"],"code":"`linetime`"},{"id":"/root/children/96/children/6","type":"text","loc":{"start":13877,"end":13883,"line":{"s":330,"e":330,"code":["See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows."]},"column":{"s":118,"e":124}},"dim":["","paragraph.96","text.6"],"code":" rows."},{"id":"/root/children/97","type":"thematicBreak","loc":{"start":13885,"end":13888,"line":{"s":332,"e":332,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.97"],"code":"---"},{"id":"/root/children/98","type":"heading","loc":{"start":13890,"end":13906,"line":{"s":334,"e":334,"code":["## `repo_voting`"]},"column":{"s":0,"e":16}},"dim":["","heading.98"],"code":"## `repo_voting`","symbName":"heading","symbRange":[13908,16362],"symbRangeL":[334,373],"outerCode":"\n**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.\n\n| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |\n\n**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`.\n\n**Key query patterns:**\n\nDashboard voting (count per ref):\n\n```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```\n\nHeading-changes metadata check (`hasMetaData`):\n\n```sql\nSELECT DISTINCT ref FROM repo_voting\nWHERE ref IN ('ref1', 'ref2', ...)\n```\n\n**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`.\n\n**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):","outerHtml":"\n<p><b>Role:</b> Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.</p>\n\n<p>| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |</p>\n\n<p><b>Indexes:</b> `num1`, `num2`, `campaign`, `operation`, `ref`.</p>\n\n<p><b>Key query patterns:</b></p>\n\n<p>Dashboard voting (count per ref):</p>\n\n<p>```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```</p>\n\n<p>Heading-changes metadata check (`hasMetaData`):</p>\n\n<p>```sql\nSELECT DISTINCT ref FROM repo_voting\nWHERE ref IN ('ref1', 'ref2', ...)\n```</p>\n\n<p><b>ref format:</b> Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`.</p>\n\n<p><b>Cleanup on file deletion</b> (ast-lake.mjs `cleanup()` at line 305):</p>"},{"id":"/root/children/98/children/0","type":"inlineCode","loc":{"start":13893,"end":13906,"line":{"s":334,"e":334,"code":["## `repo_voting`"]},"column":{"s":3,"e":16}},"dim":["","heading.98","inlineCode.0"],"code":"`repo_voting`"},{"id":"/root/children/99","type":"paragraph","loc":{"start":13908,"end":14097,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":0,"e":189}},"dim":["","paragraph.99"],"code":"**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."},{"id":"/root/children/99/children/0","type":"strong","loc":{"start":13908,"end":13917,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":0,"e":9}},"dim":["","paragraph.99","strong.0"],"code":"**Role:**"},{"id":"/root/children/99/children/0/children/0","type":"text","loc":{"start":13910,"end":13915,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":2,"e":7}},"dim":["","paragraph.99","strong.0","text.0"],"code":"Role:"},{"id":"/root/children/99/children/1","type":"text","loc":{"start":13917,"end":14039,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":9,"e":131}},"dim":["","paragraph.99","text.1"],"code":" Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from "},{"id":"/root/children/99/children/2","type":"inlineCode","loc":{"start":14039,"end":14075,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":131,"e":167}},"dim":["","paragraph.99","inlineCode.2"],"code":"`sqlCreateTableEventSource(tblName)`"},{"id":"/root/children/99/children/3","type":"text","loc":{"start":14075,"end":14079,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":167,"e":171}},"dim":["","paragraph.99","text.3"],"code":" in "},{"id":"/root/children/99/children/4","type":"inlineCode","loc":{"start":14079,"end":14096,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":171,"e":188}},"dim":["","paragraph.99","inlineCode.4"],"code":"`ast-lake.mjs:85`"},{"id":"/root/children/99/children/5","type":"text","loc":{"start":14096,"end":14097,"line":{"s":336,"e":336,"code":["**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`."]},"column":{"s":188,"e":189}},"dim":["","paragraph.99","text.5"],"code":"."},{"id":"/root/children/100","type":"paragraph","loc":{"start":14099,"end":15682,"line":{"s":338,"e":348,"code":["| Column      | Type             | Description                                                                                                |","| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |","| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |","| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |","| `user`      | TEXT             | Voter identifier (username or email)                                                                       |","| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |","| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |","| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |","| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |","| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |","| `json`      | TEXT             | Additional payload as JSON blob                                                                            |"]},"column":{"s":0,"e":143}},"dim":["","paragraph.100"],"code":"| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |"},{"id":"/root/children/100/children/0","type":"text","loc":{"start":14099,"end":14389,"line":{"s":338,"e":340,"code":["| Column      | Type             | Description                                                                                                |","| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |","| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.100","text.0"],"code":"| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/100/children/1","type":"inlineCode","loc":{"start":14389,"end":14393,"line":{"s":340,"e":340,"code":["| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.100","inlineCode.1"],"code":"`id`"},{"id":"/root/children/100/children/2","type":"text","loc":{"start":14393,"end":14533,"line":{"s":340,"e":341,"code":["| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |","| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |"]},"column":{"s":6,"e":2}},"dim":["","paragraph.100","text.2"],"code":"        | INTEGER          | Primary key, autoincrement                                                                                 |\n| "},{"id":"/root/children/100/children/3","type":"inlineCode","loc":{"start":14533,"end":14536,"line":{"s":341,"e":341,"code":["| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |"]},"column":{"s":2,"e":5}},"dim":["","paragraph.100","inlineCode.3"],"code":"`d`"},{"id":"/root/children/100/children/4","type":"text","loc":{"start":14536,"end":14677,"line":{"s":341,"e":342,"code":["| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |","| `user`      | TEXT             | Voter identifier (username or email)                                                                       |"]},"column":{"s":5,"e":2}},"dim":["","paragraph.100","text.4"],"code":"         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| "},{"id":"/root/children/100/children/5","type":"inlineCode","loc":{"start":14677,"end":14683,"line":{"s":342,"e":342,"code":["| `user`      | TEXT             | Voter identifier (username or email)                                                                       |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.100","inlineCode.5"],"code":"`user`"},{"id":"/root/children/100/children/6","type":"text","loc":{"start":14683,"end":14821,"line":{"s":342,"e":343,"code":["| `user`      | TEXT             | Voter identifier (username or email)                                                                       |","| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.100","text.6"],"code":"      | TEXT             | Voter identifier (username or email)                                                                       |\n| "},{"id":"/root/children/100/children/7","type":"inlineCode","loc":{"start":14821,"end":14826,"line":{"s":343,"e":343,"code":["| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.100","inlineCode.7"],"code":"`ref`"},{"id":"/root/children/100/children/8","type":"text","loc":{"start":14826,"end":14893,"line":{"s":343,"e":343,"code":["| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |"]},"column":{"s":7,"e":74}},"dim":["","paragraph.100","text.8"],"code":"       | TEXT NOT NULL    | Fragment refId being voted on (matches "},{"id":"/root/children/100/children/9","type":"inlineCode","loc":{"start":14893,"end":14923,"line":{"s":343,"e":343,"code":["| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |"]},"column":{"s":74,"e":104}},"dim":["","paragraph.100","inlineCode.9"],"code":"`cache_ast_lake_nodes_ref.ref`"},{"id":"/root/children/100/children/10","type":"text","loc":{"start":14923,"end":14965,"line":{"s":343,"e":344,"code":["| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |","| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":104,"e":2}},"dim":["","paragraph.100","text.10"],"code":")                                     |\n| "},{"id":"/root/children/100/children/11","type":"inlineCode","loc":{"start":14965,"end":14975,"line":{"s":344,"e":344,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.100","inlineCode.11"],"code":"`campaign`"},{"id":"/root/children/100/children/12","type":"text","loc":{"start":14975,"end":15056,"line":{"s":344,"e":344,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":12,"e":93}},"dim":["","paragraph.100","text.12"],"code":"  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. "},{"id":"/root/children/100/children/13","type":"inlineCode","loc":{"start":15056,"end":15070,"line":{"s":344,"e":344,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":93,"e":107}},"dim":["","paragraph.100","inlineCode.13"],"code":"`\"eisenhower\"`"},{"id":"/root/children/100/children/14","type":"text","loc":{"start":15070,"end":15072,"line":{"s":344,"e":344,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":107,"e":109}},"dim":["","paragraph.100","text.14"],"code":", "},{"id":"/root/children/100/children/15","type":"inlineCode","loc":{"start":15072,"end":15082,"line":{"s":344,"e":344,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":109,"e":119}},"dim":["","paragraph.100","inlineCode.15"],"code":"`\"debate\"`"},{"id":"/root/children/100/children/16","type":"text","loc":{"start":15082,"end":15084,"line":{"s":344,"e":344,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":119,"e":121}},"dim":["","paragraph.100","text.16"],"code":", "},{"id":"/root/children/100/children/17","type":"inlineCode","loc":{"start":15084,"end":15103,"line":{"s":344,"e":344,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |"]},"column":{"s":121,"e":140}},"dim":["","paragraph.100","inlineCode.17"],"code":"`\"heading-changes\"`"},{"id":"/root/children/100/children/18","type":"text","loc":{"start":15103,"end":15109,"line":{"s":344,"e":345,"code":["| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |","| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":140,"e":2}},"dim":["","paragraph.100","text.18"],"code":") |\n| "},{"id":"/root/children/100/children/19","type":"inlineCode","loc":{"start":15109,"end":15120,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":2,"e":13}},"dim":["","paragraph.100","inlineCode.19"],"code":"`operation`"},{"id":"/root/children/100/children/20","type":"text","loc":{"start":15120,"end":15184,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":13,"e":77}},"dim":["","paragraph.100","text.20"],"code":" | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. "},{"id":"/root/children/100/children/21","type":"inlineCode","loc":{"start":15184,"end":15191,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":77,"e":84}},"dim":["","paragraph.100","inlineCode.21"],"code":"`\"hot\"`"},{"id":"/root/children/100/children/22","type":"text","loc":{"start":15191,"end":15193,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":84,"e":86}},"dim":["","paragraph.100","text.22"],"code":", "},{"id":"/root/children/100/children/23","type":"inlineCode","loc":{"start":15193,"end":15200,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":86,"e":93}},"dim":["","paragraph.100","inlineCode.23"],"code":"`\"pin\"`"},{"id":"/root/children/100/children/24","type":"text","loc":{"start":15200,"end":15202,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":93,"e":95}},"dim":["","paragraph.100","text.24"],"code":", "},{"id":"/root/children/100/children/25","type":"inlineCode","loc":{"start":15202,"end":15210,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":95,"e":103}},"dim":["","paragraph.100","inlineCode.25"],"code":"`\"weak\"`"},{"id":"/root/children/100/children/26","type":"text","loc":{"start":15210,"end":15212,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":103,"e":105}},"dim":["","paragraph.100","text.26"],"code":", "},{"id":"/root/children/100/children/27","type":"inlineCode","loc":{"start":15212,"end":15220,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":105,"e":113}},"dim":["","paragraph.100","inlineCode.27"],"code":"`\"veto\"`"},{"id":"/root/children/100/children/28","type":"text","loc":{"start":15220,"end":15222,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":113,"e":115}},"dim":["","paragraph.100","text.28"],"code":", "},{"id":"/root/children/100/children/29","type":"inlineCode","loc":{"start":15222,"end":15230,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":115,"e":123}},"dim":["","paragraph.100","inlineCode.29"],"code":"`\"cast\"`"},{"id":"/root/children/100/children/30","type":"text","loc":{"start":15230,"end":15232,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":123,"e":125}},"dim":["","paragraph.100","text.30"],"code":", "},{"id":"/root/children/100/children/31","type":"inlineCode","loc":{"start":15232,"end":15242,"line":{"s":345,"e":345,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |"]},"column":{"s":125,"e":135}},"dim":["","paragraph.100","inlineCode.31"],"code":"`\"uncast\"`"},{"id":"/root/children/100/children/32","type":"text","loc":{"start":15242,"end":15253,"line":{"s":345,"e":346,"code":["| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |","| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |"]},"column":{"s":135,"e":2}},"dim":["","paragraph.100","text.32"],"code":")      |\n| "},{"id":"/root/children/100/children/33","type":"inlineCode","loc":{"start":15253,"end":15259,"line":{"s":346,"e":346,"code":["| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.100","inlineCode.33"],"code":"`num1`"},{"id":"/root/children/100/children/34","type":"text","loc":{"start":15259,"end":15397,"line":{"s":346,"e":347,"code":["| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |","| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.100","text.34"],"code":"      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| "},{"id":"/root/children/100/children/35","type":"inlineCode","loc":{"start":15397,"end":15403,"line":{"s":347,"e":347,"code":["| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.100","inlineCode.35"],"code":"`num2`"},{"id":"/root/children/100/children/36","type":"text","loc":{"start":15403,"end":15541,"line":{"s":347,"e":348,"code":["| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |","| `json`      | TEXT             | Additional payload as JSON blob                                                                            |"]},"column":{"s":8,"e":2}},"dim":["","paragraph.100","text.36"],"code":"      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| "},{"id":"/root/children/100/children/37","type":"inlineCode","loc":{"start":15541,"end":15547,"line":{"s":348,"e":348,"code":["| `json`      | TEXT             | Additional payload as JSON blob                                                                            |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.100","inlineCode.37"],"code":"`json`"},{"id":"/root/children/100/children/38","type":"text","loc":{"start":15547,"end":15682,"line":{"s":348,"e":348,"code":["| `json`      | TEXT             | Additional payload as JSON blob                                                                            |"]},"column":{"s":8,"e":143}},"dim":["","paragraph.100","text.38"],"code":"      | TEXT             | Additional payload as JSON blob                                                                            |"},{"id":"/root/children/101","type":"paragraph","loc":{"start":15684,"end":15744,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":0,"e":60}},"dim":["","paragraph.101"],"code":"**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."},{"id":"/root/children/101/children/0","type":"strong","loc":{"start":15684,"end":15696,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":0,"e":12}},"dim":["","paragraph.101","strong.0"],"code":"**Indexes:**"},{"id":"/root/children/101/children/0/children/0","type":"text","loc":{"start":15686,"end":15694,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":2,"e":10}},"dim":["","paragraph.101","strong.0","text.0"],"code":"Indexes:"},{"id":"/root/children/101/children/1","type":"text","loc":{"start":15696,"end":15697,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":12,"e":13}},"dim":["","paragraph.101","text.1"],"code":" "},{"id":"/root/children/101/children/2","type":"inlineCode","loc":{"start":15697,"end":15703,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":13,"e":19}},"dim":["","paragraph.101","inlineCode.2"],"code":"`num1`"},{"id":"/root/children/101/children/3","type":"text","loc":{"start":15703,"end":15705,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":19,"e":21}},"dim":["","paragraph.101","text.3"],"code":", "},{"id":"/root/children/101/children/4","type":"inlineCode","loc":{"start":15705,"end":15711,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":21,"e":27}},"dim":["","paragraph.101","inlineCode.4"],"code":"`num2`"},{"id":"/root/children/101/children/5","type":"text","loc":{"start":15711,"end":15713,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":27,"e":29}},"dim":["","paragraph.101","text.5"],"code":", "},{"id":"/root/children/101/children/6","type":"inlineCode","loc":{"start":15713,"end":15723,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":29,"e":39}},"dim":["","paragraph.101","inlineCode.6"],"code":"`campaign`"},{"id":"/root/children/101/children/7","type":"text","loc":{"start":15723,"end":15725,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":39,"e":41}},"dim":["","paragraph.101","text.7"],"code":", "},{"id":"/root/children/101/children/8","type":"inlineCode","loc":{"start":15725,"end":15736,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":41,"e":52}},"dim":["","paragraph.101","inlineCode.8"],"code":"`operation`"},{"id":"/root/children/101/children/9","type":"text","loc":{"start":15736,"end":15738,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":52,"e":54}},"dim":["","paragraph.101","text.9"],"code":", "},{"id":"/root/children/101/children/10","type":"inlineCode","loc":{"start":15738,"end":15743,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":54,"e":59}},"dim":["","paragraph.101","inlineCode.10"],"code":"`ref`"},{"id":"/root/children/101/children/11","type":"text","loc":{"start":15743,"end":15744,"line":{"s":350,"e":350,"code":["**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`."]},"column":{"s":59,"e":60}},"dim":["","paragraph.101","text.11"],"code":"."},{"id":"/root/children/102","type":"paragraph","loc":{"start":15746,"end":15769,"line":{"s":352,"e":352,"code":["**Key query patterns:**"]},"column":{"s":0,"e":23}},"dim":["","paragraph.102"],"code":"**Key query patterns:**"},{"id":"/root/children/102/children/0","type":"strong","loc":{"start":15746,"end":15769,"line":{"s":352,"e":352,"code":["**Key query patterns:**"]},"column":{"s":0,"e":23}},"dim":["","paragraph.102","strong.0"],"code":"**Key query patterns:**"},{"id":"/root/children/102/children/0/children/0","type":"text","loc":{"start":15748,"end":15767,"line":{"s":352,"e":352,"code":["**Key query patterns:**"]},"column":{"s":2,"e":21}},"dim":["","paragraph.102","strong.0","text.0"],"code":"Key query patterns:"},{"id":"/root/children/103","type":"paragraph","loc":{"start":15771,"end":15804,"line":{"s":354,"e":354,"code":["Dashboard voting (count per ref):"]},"column":{"s":0,"e":33}},"dim":["","paragraph.103"],"code":"Dashboard voting (count per ref):"},{"id":"/root/children/103/children/0","type":"text","loc":{"start":15771,"end":15804,"line":{"s":354,"e":354,"code":["Dashboard voting (count per ref):"]},"column":{"s":0,"e":33}},"dim":["","paragraph.103","text.0"],"code":"Dashboard voting (count per ref):"},{"id":"/root/children/104","type":"code","loc":{"start":15806,"end":15948,"line":{"s":356,"e":361,"code":["```sql","SELECT ref, operation, COUNT(*) as count, SUM(num1) as total","FROM repo_voting","WHERE campaign = 'eisenhower'","GROUP BY ref, operation","```"]},"column":{"s":0,"e":3}},"dim":["","code.104"],"code":"```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```","symbName":"code","symbRange":[15950,15997],"symbRangeL":[null,364],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):\n\n- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):\n\n```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```\n\n---\n\n### `cache_ast_lake_nodes_ref`\n\n**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.\n\n| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |\n\n**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):\n\n1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`\n2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`\n\n**`buildNodeRefId()`** (ast-lake.mjs:46-48):\n\n- For headings: returns last trail segment (e.g. `\"installation\"`)\n- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`\n\n**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.\n\n---\n\n### `cache_ast_lake_nodes_backup`\n\n**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):\n\n```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```\n\nThen the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.\n\nNote: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.\n\n---\n\n## `linetime`\n\n**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.\n\nSchema (commented inline in `ast-lake.mjs:122-131`):\n\n| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |\n\n**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):\n\n```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```\n\nThe result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.\n\nSee `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.\n\n---\n\n## `repo_voting`\n\n**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.\n\n| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |\n\n**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`.\n\n**Key query patterns:**\n\nDashboard voting (count per ref):\n\n```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```\n\nHeading-changes metadata check (`hasMetaData`):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>\n\n<ul><li><b>headings</b>: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops</li><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>\n\n<p>```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_ref`</h3>\n\n<p><b>Role:</b> Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.</p>\n\n<p>| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | <b>Readable refId</b> — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |</p>\n\n<p><b>`buildNodeAbsoluteId()` construction</b> (ast-lake.mjs:29-44):</p>\n\n<ol><li>Strips `/repo/` prefix from fn → `relative/path/file.mdd`</li><li>Appends `::` + sanitized trail segments joined by `/`</li><li>For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor</li><li>Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`</li></ol>\n\n<p><b>`buildNodeRefId()`</b> (ast-lake.mjs:46-48):</p>\n\n<ul><li>For headings: returns last trail segment (e.g. `\"installation\"`)</li><li>For links: returns nomen (link label, e.g. `\"click here\"`)</li><li>This is the short ref stored in `cache_ast_lake_nodes.ref`</li></ul>\n\n<p><b>Why two ref tables?</b> `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_backup`</h3>\n\n<p><b>Role:</b> Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):</p>\n\n<p>```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```</p>\n\n<p>Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.</p>\n\n<p>Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.</p>\n\n<hr/>\n\n<h2>`linetime`</h2>\n\n<p><b>Role:</b> Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.</p>\n\n<p>Schema (commented inline in `ast-lake.mjs:122-131`):</p>\n\n<p>| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`, ast-lake.mjs:473):</p>\n\n<p>```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```</p>\n\n<p>The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.</p>\n\n<p>See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.</p>\n\n<hr/>\n\n<h2>`repo_voting`</h2>\n\n<p><b>Role:</b> Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.</p>\n\n<p>| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |</p>\n\n<p><b>Indexes:</b> `num1`, `num2`, `campaign`, `operation`, `ref`.</p>\n\n<p><b>Key query patterns:</b></p>\n\n<p>Dashboard voting (count per ref):</p>\n\n<p>```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```</p>\n\n<p>Heading-changes metadata check (`hasMetaData`):</p>"},{"id":"/root/children/105","type":"paragraph","loc":{"start":15950,"end":15997,"line":{"s":363,"e":363,"code":["Heading-changes metadata check (`hasMetaData`):"]},"column":{"s":0,"e":47}},"dim":["","paragraph.105"],"code":"Heading-changes metadata check (`hasMetaData`):"},{"id":"/root/children/105/children/0","type":"text","loc":{"start":15950,"end":15982,"line":{"s":363,"e":363,"code":["Heading-changes metadata check (`hasMetaData`):"]},"column":{"s":0,"e":32}},"dim":["","paragraph.105","text.0"],"code":"Heading-changes metadata check ("},{"id":"/root/children/105/children/1","type":"inlineCode","loc":{"start":15982,"end":15995,"line":{"s":363,"e":363,"code":["Heading-changes metadata check (`hasMetaData`):"]},"column":{"s":32,"e":45}},"dim":["","paragraph.105","inlineCode.1"],"code":"`hasMetaData`"},{"id":"/root/children/105/children/2","type":"text","loc":{"start":15995,"end":15997,"line":{"s":363,"e":363,"code":["Heading-changes metadata check (`hasMetaData`):"]},"column":{"s":45,"e":47}},"dim":["","paragraph.105","text.2"],"code":"):"},{"id":"/root/children/106","type":"code","loc":{"start":15999,"end":16081,"line":{"s":365,"e":368,"code":["```sql","SELECT DISTINCT ref FROM repo_voting","WHERE ref IN ('ref1', 'ref2', ...)","```"]},"column":{"s":0,"e":3}},"dim":["","code.106"],"code":"```sql\nSELECT DISTINCT ref FROM repo_voting\nWHERE ref IN ('ref1', 'ref2', ...)\n```","symbName":"code","symbRange":[16083,16362],"symbRangeL":[null,373],"outerCode":";{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }\n\n# ast-lake\n\nusage: ast-lake [command] [args...]\n\n- [123](http://xxx)\n- [xxx](#/papers/@dashboard)\n\n## commands:\n\n### info\n\n- info on the state of ast-lake\n\n### init\n\n- precreate the cache db\n\n### reset\n\n- delete list\n- delete all cached data\n\n### cleanup\n\n- remove non existent files from sqlite cache\n\n### restart\n\n- keep list\n- delete all cached data\n\n#### reset-nodes\n\n- cleanup only cached nodes (dev)\n- and recreated them\n\n### add\n\n- add files to list\n- dbPopulateList is a temporary list file, listing absolute file paths\n- dbPopulateList is usually `/tmp/ast-list.txt`\n- add absolute file paths:\n- note: does use `fd`. honors `.gitignore`\n- usage:\n\n```bash\nast-lake add '*.mdd' '*.json'\n```\n\n- will add `**/*.mdd` and `*.json` to list\n\n- or manually:\n- you may edit $dbPopulateList\n\n```bash\n# --base-directory $dir\n\nfd --absolute-path -tf \".+[.](mdd)$\" | rg -v test > $dbPopulateList\n\nfd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList\n\n# or only add arbitrary files:\n\nrg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList\n\n```\n\n- see also `ast-lake populate`\n\n### populate\n\n- ensure all ast's (from on the list) are up to date\n\n### query TERM\n\n- ast-lake query term\n- TODO term patterns ?\n- TODO regex: ^cs.+$\n\n### queryraw TERM\n\n- same as query, but returns raw JSON\n\n### flast\n\n- basically a dump\n- print all raw flASTs\n\n### nodes\n\n- print raw nodes\n\n### mdg\n\n- TODO really?\n- make one big mdg graph from all mdd's\n\n### mosaic\n\n- mosaic\n- TODO really?\n\n### mprocs\n\n- TODO support mprocs\n\n## recent updates\n\n### 2025-12-01\n\n- actualizeNodes: chunked parallel processing\n- chunk size: CPU count / 2\n- faster database query processing\n\n# db tables\n\nThese tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.\n\n**Two parallel implementations exist:**\n\n- **Shell-based** (`ast-lake.sh`) — older approach using local SQLite + nljson files\n- **Node.js-based** (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`\n\nBoth follow the same pipeline: extract flast → normalize → store nodes.\n\n## The pipeline (how data flows)\n\n```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```\n\n## `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`\n\nSchema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but **each column has different semantics per table**:\n\n| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |\n\n**Shared indexes:** `num1`, `num2`, `sem`, `ref` (each named `{table}_idx_{col}`).\n\n---\n\n### `cache_ast_lake_flast`\n\n**Role:** Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.\n\n| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |\n\n**How it's populated:** Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.\n\n**Query pattern** (in `actualizeNodes()`):\n\n```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```\n\nOnly changed files (newer mtime) are reprocessed.\n\n**Node value structure** (each element in the JSON flast array):\n\n```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```\n\n---\n\n### `cache_ast_lake_nodes`\n\n**Role:** Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.\n\n| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |\n\n**Normalized node value structure** (the JSON in the `value` column):\n\n```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```\n\n**Key normalization details** (from `normalizeNodes()` at ast-lake.mjs:183):\n\n- **headings**: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops\n- **links**: `parseMarkdownLink()` extracts `[label](url)`; `outerCode` = url; nomen = label\n- **git blame**: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author\n- Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out\n\n**Query pattern** (voting dashboard):\n\n```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```\n\n---\n\n### `cache_ast_lake_nodes_ref`\n\n**Role:** Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.\n\n| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | **Readable refId** — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |\n\n**`buildNodeAbsoluteId()` construction** (ast-lake.mjs:29-44):\n\n1. Strips `/repo/` prefix from fn → `relative/path/file.mdd`\n2. Appends `::` + sanitized trail segments joined by `/`\n3. For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor\n4. Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`\n\n**`buildNodeRefId()`** (ast-lake.mjs:46-48):\n\n- For headings: returns last trail segment (e.g. `\"installation\"`)\n- For links: returns nomen (link label, e.g. `\"click here\"`)\n- This is the short ref stored in `cache_ast_lake_nodes.ref`\n\n**Why two ref tables?** `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.\n\n---\n\n### `cache_ast_lake_nodes_backup`\n\n**Role:** Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):\n\n```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```\n\nThen the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.\n\nNote: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.\n\n---\n\n## `linetime`\n\n**Role:** Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.\n\nSchema (commented inline in `ast-lake.mjs:122-131`):\n\n| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |\n\n**Query pattern** (in `actualizeNodes()`, ast-lake.mjs:473):\n\n```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```\n\nThe result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.\n\nSee `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.\n\n---\n\n## `repo_voting`\n\n**Role:** Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.\n\n| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |\n\n**Indexes:** `num1`, `num2`, `campaign`, `operation`, `ref`.\n\n**Key query patterns:**\n\nDashboard voting (count per ref):\n\n```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```\n\nHeading-changes metadata check (`hasMetaData`):\n\n```sql\nSELECT DISTINCT ref FROM repo_voting\nWHERE ref IN ('ref1', 'ref2', ...)\n```\n\n**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`.\n\n**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):","outerHtml":"<p>;{ engine: dot rankdir: LR useGvPack: true clusterize: true intention: semi nodeNomen: absolute applyCss: true }</p>\n\n<h1>ast-lake</h1>\n\n<p>usage: ast-lake [command] [args...]</p>\n\n<ul><li><a href=\"http://xxx\">123</a></li><li><a href=\"#/papers/@dashboard\">xxx</a></li></ul>\n\n<h2>commands:</h2>\n\n<h3>info</h3>\n\n<ul><li>info on the state of ast-lake</li></ul>\n\n<h3>init</h3>\n\n<ul><li>precreate the cache db</li></ul>\n\n<h3>reset</h3>\n\n<ul><li>delete list</li><li>delete all cached data</li></ul>\n\n<h3>cleanup</h3>\n\n<ul><li>remove non existent files from sqlite cache</li></ul>\n\n<h3>restart</h3>\n\n<ul><li>keep list</li><li>delete all cached data</li></ul>\n\n<h4>reset-nodes</h4>\n\n<ul><li>cleanup only cached nodes (dev)</li><li>and recreated them</li></ul>\n\n<h3>add</h3>\n\n<ul><li>add files to list</li><li>dbPopulateList is a temporary list file, listing absolute file paths</li><li>dbPopulateList is usually `/tmp/ast-list.txt`</li><li>add absolute file paths:</li><li>note: does use `fd`. honors `.gitignore`</li><li>usage:</li></ul>\n\n<p>```bash\nast-lake add '<i>.mdd' '</i>.json'\n```</p>\n\n<ul><li>will add `<i></i>/*.mdd` and `*.json` to list</li></ul>\n\n<ul><li>or manually:</li><li>you may edit $dbPopulateList</li></ul>\n\n<p>```bash</p><h1>--base-directory $dir</h1>\n\n<p>fd --absolute-path -tf \".+<a href=\"mdd\">.</a>$\" | rg -v test > $dbPopulateList</p>\n\n<p>fd -tf --absolute-path \"$filterFilesPattern\" > $dbPopulateList | rg -v node_modules | rg -v test > $dbPopulateList</p>\n\n<h1>or only add arbitrary files:</h1>\n\n<p>rg TERM --files-with-matches | xargs realpath {} >> $dbPopulateList</p>\n\n<p>```</p>\n\n<ul><li>see also `ast-lake populate`</li></ul>\n\n<h3>populate</h3>\n\n<ul><li>ensure all ast's (from on the list) are up to date</li></ul>\n\n<h3>query TERM</h3>\n\n<ul><li>ast-lake query term</li><li>TODO term patterns ?</li><li>TODO regex: ^cs.+$</li></ul>\n\n<h3>queryraw TERM</h3>\n\n<ul><li>same as query, but returns raw JSON</li></ul>\n\n<h3>flast</h3>\n\n<ul><li>basically a dump</li><li>print all raw flASTs</li></ul>\n\n<h3>nodes</h3>\n\n<ul><li>print raw nodes</li></ul>\n\n<h3>mdg</h3>\n\n<ul><li>TODO really?</li><li>make one big mdg graph from all mdd's</li></ul>\n\n<h3>mosaic</h3>\n\n<ul><li>mosaic</li><li>TODO really?</li></ul>\n\n<h3>mprocs</h3>\n\n<ul><li>TODO support mprocs</li></ul>\n\n<h2>recent updates</h2>\n\n<h3>2025-12-01</h3>\n\n<ul><li>actualizeNodes: chunked parallel processing</li><li>chunk size: CPU count / 2</li><li>faster database query processing</li></ul>\n\n<h1>db tables</h1>\n\n<p>These tables are in our ast-lake SQLite database. They hold the state derived from parsing mdd/md/js files into AST nodes.</p>\n\n<p><b>Two parallel implementations exist:</b></p>\n\n<ul><li><b>Shell-based</b> (`ast-lake.sh`) — older approach using local SQLite + nljson files</li><li><b>Node.js-based</b> (`ast-lake.mjs`) — newer approach using remote SQLite via `RemoteSqlite`</li></ul>\n\n<p>Both follow the same pipeline: extract flast → normalize → store nodes.</p>\n\n<h2>The pipeline (how data flows)</h2>\n\n<p>```\nFile on disk\n  │\n  ▼\nastHole() → astNavigator() — reads file, calls language-specific extractor\n  │                            (extractFlastMarkdown, extractFlastJavascript,\n  │                             extractFlastJson, extractFlastBash, ...)\n  ▼\ncache_ast_lake_flast          — one row per file; id=filepath, value=JSON flast array\n  │\n  ▼\nactualizeNodes() → normalizeNodes() — extracts symbolic types from flast\n  │                                   (heading, link, comment, FunctionDeclaration, Identifier)\n  ▼\ncache_ast_lake_nodes          — one row per AST node; id=fn:offset\ncache_ast_lake_nodes_ref      — same rows, different ref format (readable refId)\ncache_ast_lake_nodes_backup   — backup of old nodes before update\n```</p>\n\n<h2>`cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`</h2>\n\n<p>Schema from `sqlCreateTableCache(tblName)` in `ast-lake.mjs:51`. All four share the same column layout but <b>each column has different semantics per table</b>:</p>\n\n<p>| Column  | Type          | Description                              |\n| ------- | ------------- | ---------------------------------------- |\n| `id`    | TEXT NOT NULL | Primary key                              |\n| `value` | TEXT          | JSON payload of the node or flast        |\n| `mt`    | TEXT          | Modification timestamp or mime-type      |\n| `sem`   | TEXT          | Semantic type                            |\n| `num1`  | INTEGER       | First numeric slot (position or line)    |\n| `num2`  | INTEGER       | Second numeric slot (position or column) |\n| `ref`   | TEXT          | Reference ID (trail or refId)            |\n| `body`  | TEXT          | Node body text                           |</p>\n\n<p><b>Shared indexes:</b> `num1`, `num2`, `sem`, `ref` (each named `{table}<i>idx</i>{col}`).</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_flast`</h3>\n\n<p><b>Role:</b> Raw flattened AST cache — one row per file. Stores the complete AST produced by the language-specific extractor.</p>\n\n<p>| Column  | How it's populated (from `actualizeNodes()`)   | Semantics                                                                      |\n| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------ |\n| `id`    | `row[0]` — the file path (fn)                  | Full filesystem path to the source file                                        |\n| `value` | `row[1]` — `JSON.parse(row[1])` → `data.flast` | Full JSON array of all AST nodes extracted from the file (the \"flattened AST\") |\n| `mt`    | `row[2]` — file modification timestamp         | ISO timestamp of last file modification                                        |</p>\n\n<p><b>How it's populated:</b> Written by `astNavigator.mjs` via `cacheFlast(fnIn, data)` (line 1571), using `initSqliteCache` from `sqlito.mjs`. The `data.value` is the array of fragments produced by extractors like `extractFlastMarkdown`, `extractFlastJavascript`, etc.</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`):</p>\n\n<p>```sql\nSELECT id, value, mt FROM cache_ast_lake_flast WHERE unixepoch(mt) > <lastSync>\n```</p>\n\n<p>Only changed files (newer mtime) are reprocessed.</p>\n\n<p><b>Node value structure</b> (each element in the JSON flast array):</p>\n\n<p>```json\n{\n  \"id\": \"node-id\",\n  \"dim\": [\"file\", \"listItem.0\", \"property.key\"],\n  \"type\": \"heading|link|code|paragraph|list|...\",\n  \"code\": \"raw text content\",\n  \"loc\": { \"line\": { \"s\": 1, \"e\": 3 }, \"column\": { \"s\": 1, \"e\": 80 } },\n  \"symbName\": null,\n  \"symbRangeL\": null\n}\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes`</h3>\n\n<p><b>Role:</b> Individual AST node storage — one row per symbolic node. Produced by `normalizeNodes()` in `actualizeNodes()` (ast-lake.mjs:183-266), which extracts only `symbolicTypes` (heading, link, comment, FunctionDeclaration, Identifier) from the flast.</p>\n\n<p>| Column  | How it's populated (ast-lake.mjs:516-561) | Semantics                                                                                                                     |\n| ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `id`    | `` `${node.fn}:${node.offset}` ``         | File path + offset position → `buildNodeAbsoluteId()`                                                                         |\n| `value` | `JSON.stringify(node)`                    | Full normalized node as JSON (see structure below)                                                                            |\n| `mt`    | `node.mt`                                 | Modification time (symbMt from git blame, or file mtime)                                                                      |\n| `sem`   | `node.type`                               | Node type: `\"heading\"`, `\"link\"`, `\"comment\"`, `\"FunctionDeclaration\"`, `\"Identifier\"`                                        |\n| `num1`  | `node.position[0]`                        | Line number in source file (1-based)                                                                                          |\n| `num2`  | `node.position[1]`                        | Column number in source file (1-based)                                                                                        |\n| `ref`   | `buildNodeRefId(node)`                    | For headings: last trail segment (heading text). For links: nomen (link label). Defines the short ref used in voting queries. |\n| `body`  | `node.outerCode \\|\\| node.code \\|\\| ''`   | Full text content of the node. For headings: the entire heading subtree text. For links: the markdown link syntax.            |</p>\n\n<p><b>Normalized node value structure</b> (the JSON in the `value` column):</p>\n\n<p>```json\n{\n  \"fn\": \"/home/.../repo/repolero/paper/file.mdd\",\n  \"trail\": [\"getting-started\", \"installation\"],\n  \"type\": \"heading\",\n  \"nomen\": \"Installation\",\n  \"code\": \"## Installation\",\n  \"symbName\": null,\n  \"symbBlame\": [\"abc123def\", \"456ghi789\"],\n  \"outerCode\": \"full subtree text...\",\n  \"position\": [42, 1],\n  \"lineRange\": [42, 85],\n  \"offset\": 42,\n  \"symbMt\": \"2025-06-15T10:30:00Z\",\n  \"symbUser\": \"author@example.com\",\n  \"mt\": \"2025-06-15T10:30:00Z\"\n}\n```</p>\n\n<p><b>Key normalization details</b> (from `normalizeNodes()` at ast-lake.mjs:183):</p>\n\n<ul><li><b>headings</b>: `processHeading()` extracts level and pure text; breadcrumb trail tracks hierarchy; `currentLevel > lastLevel` pushes, `currentLevel <= lastLevel` pops</li><li><b>links</b>: `parseMarkdownLink()` extracts `<a href=\"url\">label</a>`; `outerCode` = url; nomen = label</li><li><b>git blame</b>: `symbBlame` array of commit hashes for lines in `symbRangeL`; `symbMt` = most recent blame mtime; `symbUser` = most recent author</li><li>Only nodes with types in `symbolicTypes` are kept; all others (paragraph, list, code without heading context, etc.) are filtered out</li></ul>\n\n<p><b>Query pattern</b> (voting dashboard):</p>\n\n<p>```sql\nSELECT n.id, n.body, n.num1 as headingLevel, r.ref\nFROM cache_ast_lake_nodes n\nINNER JOIN cache_ast_lake_nodes_ref r USING(id)\nINNER JOIN repo_voting v ON v.ref = r.ref\nWHERE n.sem = 'heading'\n```</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_ref`</h3>\n\n<p><b>Role:</b> Readable refId mirror of nodes. Stores the user-visible fragment reference string (the one used in URLs and voting) instead of the last-trail-segment ref.</p>\n\n<p>| Column | How it's populated (ast-lake.mjs:560)      | Semantics                                                                                                                                                                    |\n| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`   | Same as nodes: `fn:offset`                 | Joins to `cache_ast_lake_nodes.id`                                                                                                                                           |\n| `sem`  | `node.type`                                | Node type (same as nodes.sem)                                                                                                                                                |\n| `ref`  | `buildNodeAbsoluteId(node)`                | <b>Readable refId</b> — format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`. Built by `buildNodeAbsoluteId()` (ast-lake.mjs:29) |\n| `body` | `linkUrl` for links, `repoName` for others | For links: the URL target. For headings: the repo name.                                                                                                                      |</p>\n\n<p><b>`buildNodeAbsoluteId()` construction</b> (ast-lake.mjs:29-44):</p>\n\n<ol><li>Strips `/repo/` prefix from fn → `relative/path/file.mdd`</li><li>Appends `::` + sanitized trail segments joined by `/`</li><li>For links: appends `#sanitizeName(nomen):tinyHash(url)` anchor</li><li>Result example: `paper/visualization.mdd::visualization/graphviz#osage:ab12cd`</li></ol>\n\n<p><b>`buildNodeRefId()`</b> (ast-lake.mjs:46-48):</p>\n\n<ul><li>For headings: returns last trail segment (e.g. `\"installation\"`)</li><li>For links: returns nomen (link label, e.g. `\"click here\"`)</li><li>This is the short ref stored in `cache_ast_lake_nodes.ref`</li></ul>\n\n<p><b>Why two ref tables?</b> `cache_ast_lake_nodes.ref` has the short, per-node ref (last trail segment). `cache_ast_lake_nodes_ref.ref` has the full, human-readable fragment path including the file prefix. The voting system uses the full refId from `nodes_ref`.</p>\n\n<hr/>\n\n<h3>`cache_ast_lake_nodes_backup`</h3>\n\n<p><b>Role:</b> Rollback copy. Before updating a file's nodes in `cache_ast_lake_nodes`, the old rows are copied here (ast-lake.mjs:500-514):</p>\n\n<p>```sql\nINSERT OR REPLACE INTO cache_ast_lake_nodes_backup\n  (id, value, mt, sem, num1, num2, ref, body)\nSELECT id, value, mt, sem, num1, num2, ref, body\nFROM cache_ast_lake_nodes\nWHERE id LIKE '<escapedFn>:%'\n```</p>\n\n<p>Then the old rows are deleted from both `cache_ast_lake_nodes` and `cache_ast_lake_nodes_ref` before inserting the new normalized nodes. If a sync fails, the backup can be restored.</p>\n\n<p>Note: `cache_ast_lake_nodes_ref` does NOT have a backup table — the ref table is rebuilt from scratch each time.</p>\n\n<hr/>\n\n<h2>`linetime`</h2>\n\n<p><b>Role:</b> Git blame line-history cache. Maps each line in each file to its git blame metadata (commit hash, author, timestamp). Used by `normalizeNodes()` to attach `symbBlame`/`symbMt`/`symbUser` to each node.</p>\n\n<p>Schema (commented inline in `ast-lake.mjs:122-131`):</p>\n\n<p>| Column | Type             | Description                                           |\n| ------ | ---------------- | ----------------------------------------------------- |\n| `id`   | INTEGER          | Primary key, autoincrement                            |\n| `fn`   | TEXT NOT NULL    | File name / path (matches flast.id / nodes fn prefix) |\n| `line` | INTEGER NOT NULL | Line number in the file (1-based)                     |\n| `hash` | TEXT NOT NULL    | Git commit hash from blame                            |\n| `mt`   | TEXT NOT NULL    | Commit author timestamp                               |\n| `user` | TEXT             | Author name or email                                  |\n| `json` | TEXT             | Additional metadata as JSON blob                      |</p>\n\n<p><b>Query pattern</b> (in `actualizeNodes()`, ast-lake.mjs:473):</p>\n\n<p>```sql\nSELECT line, mt, user, hash FROM linetime\nWHERE fn='<filepath>' AND hash!=\"\" ORDER BY line\n```</p>\n\n<p>The result is used to compute `symbRangeL` → `symbBlame` for each node: all commit hashes for lines within the node's source range.</p>\n\n<p>See `linetime-parse-blame.jq` for the processing logic that parses `git blame --line-porcelain` output into `linetime` rows.</p>\n\n<hr/>\n\n<h2>`repo_voting`</h2>\n\n<p><b>Role:</b> Voting event log. Records voting actions (cast, uncast, hot, pin, weak, veto) on fragments within campaigns. Schema from `sqlCreateTableEventSource(tblName)` in `ast-lake.mjs:85`.</p>\n\n<p>| Column      | Type             | Description                                                                                                |\n| ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `id`        | INTEGER          | Primary key, autoincrement                                                                                 |\n| `d`         | TEXT NOT NULL    | ISO timestamp of the vote event                                                                            |\n| `user`      | TEXT             | Voter identifier (username or email)                                                                       |\n| `ref`       | TEXT NOT NULL    | Fragment refId being voted on (matches `cache_ast_lake_nodes_ref.ref`)                                     |\n| `campaign`  | TEXT NOT NULL    | Campaign scope — determines which vote UI is active (e.g. `\"eisenhower\"`, `\"debate\"`, `\"heading-changes\"`) |\n| `operation` | TEXT NOT NULL    | Vote operation — quadrant or action (e.g. `\"hot\"`, `\"pin\"`, `\"weak\"`, `\"veto\"`, `\"cast\"`, `\"uncast\"`)      |\n| `num1`      | INTEGER NOT NULL | Generic numeric slot — used for vote weight or count                                                       |\n| `num2`      | INTEGER NOT NULL | Generic numeric slot — used for secondary count                                                            |\n| `json`      | TEXT             | Additional payload as JSON blob                                                                            |</p>\n\n<p><b>Indexes:</b> `num1`, `num2`, `campaign`, `operation`, `ref`.</p>\n\n<p><b>Key query patterns:</b></p>\n\n<p>Dashboard voting (count per ref):</p>\n\n<p>```sql\nSELECT ref, operation, COUNT(*) as count, SUM(num1) as total\nFROM repo_voting\nWHERE campaign = 'eisenhower'\nGROUP BY ref, operation\n```</p>\n\n<p>Heading-changes metadata check (`hasMetaData`):</p>\n\n<p>```sql\nSELECT DISTINCT ref FROM repo_voting\nWHERE ref IN ('ref1', 'ref2', ...)\n```</p>\n\n<p><b>ref format:</b> Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`.</p>\n\n<p><b>Cleanup on file deletion</b> (ast-lake.mjs `cleanup()` at line 305):</p>"},{"id":"/root/children/107","type":"paragraph","loc":{"start":16083,"end":16292,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":0,"e":209}},"dim":["","paragraph.107"],"code":"**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."},{"id":"/root/children/107/children/0","type":"strong","loc":{"start":16083,"end":16098,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":0,"e":15}},"dim":["","paragraph.107","strong.0"],"code":"**ref format:**"},{"id":"/root/children/107/children/0/children/0","type":"text","loc":{"start":16085,"end":16096,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":2,"e":13}},"dim":["","paragraph.107","strong.0","text.0"],"code":"ref format:"},{"id":"/root/children/107/children/1","type":"text","loc":{"start":16098,"end":16110,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":15,"e":27}},"dim":["","paragraph.107","text.1"],"code":" Must match "},{"id":"/root/children/107/children/2","type":"inlineCode","loc":{"start":16110,"end":16140,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":27,"e":57}},"dim":["","paragraph.107","inlineCode.2"],"code":"`cache_ast_lake_nodes_ref.ref`"},{"id":"/root/children/107/children/3","type":"text","loc":{"start":16140,"end":16167,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":57,"e":84}},"dim":["","paragraph.107","text.3"],"code":" — the readable refId from "},{"id":"/root/children/107/children/4","type":"inlineCode","loc":{"start":16167,"end":16190,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":84,"e":107}},"dim":["","paragraph.107","inlineCode.4"],"code":"`buildNodeAbsoluteId()`"},{"id":"/root/children/107/children/5","type":"text","loc":{"start":16190,"end":16200,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":107,"e":117}},"dim":["","paragraph.107","text.5"],"code":". Format: "},{"id":"/root/children/107/children/6","type":"inlineCode","loc":{"start":16200,"end":16246,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":117,"e":163}},"dim":["","paragraph.107","inlineCode.6"],"code":"`relative/path/file.mdd::sanitized/trail/path`"},{"id":"/root/children/107/children/7","type":"text","loc":{"start":16246,"end":16259,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":163,"e":176}},"dim":["","paragraph.107","text.7"],"code":". For links: "},{"id":"/root/children/107/children/8","type":"inlineCode","loc":{"start":16259,"end":16291,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":176,"e":208}},"dim":["","paragraph.107","inlineCode.8"],"code":"`...::trail#nomen:tinyHash(url)`"},{"id":"/root/children/107/children/9","type":"text","loc":{"start":16291,"end":16292,"line":{"s":370,"e":370,"code":["**ref format:** Must match `cache_ast_lake_nodes_ref.ref` — the readable refId from `buildNodeAbsoluteId()`. Format: `relative/path/file.mdd::sanitized/trail/path`. For links: `...::trail#nomen:tinyHash(url)`."]},"column":{"s":208,"e":209}},"dim":["","paragraph.107","text.9"],"code":"."},{"id":"/root/children/108","type":"paragraph","loc":{"start":16294,"end":16362,"line":{"s":372,"e":372,"code":["**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):"]},"column":{"s":0,"e":68}},"dim":["","paragraph.108"],"code":"**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):"},{"id":"/root/children/108/children/0","type":"strong","loc":{"start":16294,"end":16322,"line":{"s":372,"e":372,"code":["**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):"]},"column":{"s":0,"e":28}},"dim":["","paragraph.108","strong.0"],"code":"**Cleanup on file deletion**"},{"id":"/root/children/108/children/0/children/0","type":"text","loc":{"start":16296,"end":16320,"line":{"s":372,"e":372,"code":["**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):"]},"column":{"s":2,"e":26}},"dim":["","paragraph.108","strong.0","text.0"],"code":"Cleanup on file deletion"},{"id":"/root/children/108/children/1","type":"text","loc":{"start":16322,"end":16337,"line":{"s":372,"e":372,"code":["**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):"]},"column":{"s":28,"e":43}},"dim":["","paragraph.108","text.1"],"code":" (ast-lake.mjs "},{"id":"/root/children/108/children/2","type":"inlineCode","loc":{"start":16337,"end":16348,"line":{"s":372,"e":372,"code":["**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):"]},"column":{"s":43,"e":54}},"dim":["","paragraph.108","inlineCode.2"],"code":"`cleanup()`"},{"id":"/root/children/108/children/3","type":"text","loc":{"start":16348,"end":16362,"line":{"s":372,"e":372,"code":["**Cleanup on file deletion** (ast-lake.mjs `cleanup()` at line 305):"]},"column":{"s":54,"e":68}},"dim":["","paragraph.108","text.3"],"code":" at line 305):"},{"id":"/root/children/109","type":"list","loc":{"start":16364,"end":16701,"line":{"s":374,"e":376,"code":["- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)","- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`","- Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"]},"column":{"s":0,"e":114}},"dim":["","list.109"],"code":"- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)\n- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`\n- Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`","symbName":"list","symbRange":[null,null],"symbRangeL":[374,376],"outerCode":"- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`","outerHtml":"<ul><li>Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`</li></ul>"},{"id":"/root/children/109/children/0","type":"listItem","loc":{"start":16364,"end":16445,"line":{"s":374,"e":374,"code":["- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)"]},"column":{"s":0,"e":81}},"dim":["","list.109","listItem.0"],"code":"- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)"},{"id":"/root/children/109/children/0/children/0","type":"paragraph","loc":{"start":16366,"end":16445,"line":{"s":374,"e":374,"code":["- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)"]},"column":{"s":2,"e":81}},"dim":["","list.109","listItem.0","paragraph.0"],"code":"Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)"},{"id":"/root/children/109/children/0/children/0/children/0","type":"text","loc":{"start":16366,"end":16382,"line":{"s":374,"e":374,"code":["- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)"]},"column":{"s":2,"e":18}},"dim":["","list.109","listItem.0","paragraph.0","text.0"],"code":"Does NOT delete "},{"id":"/root/children/109/children/0/children/0/children/1","type":"inlineCode","loc":{"start":16382,"end":16395,"line":{"s":374,"e":374,"code":["- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)"]},"column":{"s":18,"e":31}},"dim":["","list.109","listItem.0","paragraph.0","inlineCode.1"],"code":"`repo_voting`"},{"id":"/root/children/109/children/0/children/0/children/2","type":"text","loc":{"start":16395,"end":16445,"line":{"s":374,"e":374,"code":["- Does NOT delete `repo_voting` rows when a file is removed (votes are preserved)"]},"column":{"s":31,"e":81}},"dim":["","list.109","listItem.0","paragraph.0","text.2"],"code":" rows when a file is removed (votes are preserved)"},{"id":"/root/children/109/children/1","type":"listItem","loc":{"start":16446,"end":16586,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":0,"e":140}},"dim":["","list.109","listItem.1"],"code":"- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"},{"id":"/root/children/109/children/1/children/0","type":"paragraph","loc":{"start":16448,"end":16586,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":2,"e":140}},"dim":["","list.109","listItem.1","paragraph.0"],"code":"Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"},{"id":"/root/children/109/children/1/children/0/children/0","type":"text","loc":{"start":16448,"end":16465,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":2,"e":19}},"dim":["","list.109","listItem.1","paragraph.0","text.0"],"code":"Does DELETE from "},{"id":"/root/children/109/children/1/children/0/children/1","type":"inlineCode","loc":{"start":16465,"end":16487,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":19,"e":41}},"dim":["","list.109","listItem.1","paragraph.0","inlineCode.1"],"code":"`cache_ast_lake_flast`"},{"id":"/root/children/109/children/1/children/0/children/2","type":"text","loc":{"start":16487,"end":16489,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":41,"e":43}},"dim":["","list.109","listItem.1","paragraph.0","text.2"],"code":", "},{"id":"/root/children/109/children/1/children/0/children/3","type":"inlineCode","loc":{"start":16489,"end":16511,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":43,"e":65}},"dim":["","list.109","listItem.1","paragraph.0","inlineCode.3"],"code":"`cache_ast_lake_nodes`"},{"id":"/root/children/109/children/1/children/0/children/4","type":"text","loc":{"start":16511,"end":16513,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":65,"e":67}},"dim":["","list.109","listItem.1","paragraph.0","text.4"],"code":", "},{"id":"/root/children/109/children/1/children/0/children/5","type":"inlineCode","loc":{"start":16513,"end":16539,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":67,"e":93}},"dim":["","list.109","listItem.1","paragraph.0","inlineCode.5"],"code":"`cache_ast_lake_nodes_ref`"},{"id":"/root/children/109/children/1/children/0/children/6","type":"text","loc":{"start":16539,"end":16541,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":93,"e":95}},"dim":["","list.109","listItem.1","paragraph.0","text.6"],"code":", "},{"id":"/root/children/109/children/1/children/0/children/7","type":"inlineCode","loc":{"start":16541,"end":16570,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":95,"e":124}},"dim":["","list.109","listItem.1","paragraph.0","inlineCode.7"],"code":"`cache_ast_lake_nodes_backup`"},{"id":"/root/children/109/children/1/children/0/children/8","type":"text","loc":{"start":16570,"end":16576,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":124,"e":130}},"dim":["","list.109","listItem.1","paragraph.0","text.8"],"code":", and "},{"id":"/root/children/109/children/1/children/0/children/9","type":"inlineCode","loc":{"start":16576,"end":16586,"line":{"s":375,"e":375,"code":["- Does DELETE from `cache_ast_lake_flast`, `cache_ast_lake_nodes`, `cache_ast_lake_nodes_ref`, `cache_ast_lake_nodes_backup`, and `linetime`"]},"column":{"s":130,"e":140}},"dim":["","list.109","listItem.1","paragraph.0","inlineCode.9"],"code":"`linetime`"},{"id":"/root/children/109/children/2","type":"listItem","loc":{"start":16587,"end":16701,"line":{"s":376,"e":376,"code":["- Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"]},"column":{"s":0,"e":114}},"dim":["","list.109","listItem.2"],"code":"- Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"},{"id":"/root/children/109/children/2/children/0","type":"paragraph","loc":{"start":16589,"end":16701,"line":{"s":376,"e":376,"code":["- Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"]},"column":{"s":2,"e":114}},"dim":["","list.109","listItem.2","paragraph.0"],"code":"Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"},{"id":"/root/children/109/children/2/children/0/children/0","type":"text","loc":{"start":16589,"end":16609,"line":{"s":376,"e":376,"code":["- Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"]},"column":{"s":2,"e":22}},"dim":["","list.109","listItem.2","paragraph.0","text.0"],"code":"Sweeps orphan refs: "},{"id":"/root/children/109/children/2/children/0/children/1","type":"inlineCode","loc":{"start":16609,"end":16701,"line":{"s":376,"e":376,"code":["- Sweeps orphan refs: `DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"]},"column":{"s":22,"e":114}},"dim":["","list.109","listItem.2","paragraph.0","inlineCode.1"],"code":"`DELETE FROM cache_ast_lake_nodes_ref WHERE id NOT IN (SELECT id FROM cache_ast_lake_nodes)`"}]