cli+docs: --api surface index; route CLAUDE.md to llms.txt; name the lib HOFs in BUILTINS.md (#734) - #803
Conversation
…lib HOFs in BUILTINS.md (#734) Item 3 (the substantial one): `eigenscript --api [--json]` answers "does X exist, and is it builtin / extension / lib" in one call — the missing introspection leg beside --lint --json and --test --json. - builtins: enumerated from a scratch register_builtins env, never a hand list (#459: the old hand-copied array drifted ~120 names). - extensions: ext_names.h by group (gfx/http/http_request/db/model/ net) — the LANGUAGE surface, independent of build flags. - lib: every public top-level define of lib/*.eigs WITH its parameter list as written (defaults included; the no-paren implicit-`n` form reports `n`), scraped from the same candidate dirs the import resolver searches (shared helper lib_candidate_dirs, refactored out of the W021 table builder). 249 builtins + 86 extension names + 815 lib functions on this tree. Text mode is greppable; --json is the machine form. The scratch env is freed explicitly (LSan can't trace tagged slots — same rule as builtin_name_env_free). Item 1: CLAUDE.md's "Writing .eigs?" route now names docs/llms.txt — the repo's best AI-facing reference was previously reachable only from README.md line 424, invisible to exactly the agent most likely to work here. llms.txt's helper section points at --api. Item 2: BUILTINS.md's builtins-vs-library note now names map/filter/ reduce explicitly (the greps that returned zero hits) with both access forms, and notes the sort_by-is-a-builtin contrast that made the omission actively misleading. Suite [131]: pins the three kinds, the params capture, JSON validity (python3-gated), and the filter/sort_by split. Closes #734 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new CLI introspection surface (eigenscript --api [--json]) to mechanically resolve whether a name exists and whether it’s a builtin, extension (by group), or stdlib (lib/*.eigs) function, and updates docs to route AI/workflows toward the right references.
Changes:
- Introduce
--api [--json]CLI flag and implement surface-index emission (builtins, extensions-by-group, and stdlib function signatures). - Refactor/import-resolver-aligned lib directory discovery into a shared helper used by both W021 and
--api. - Update docs + changelog + test suite to document and pin the new index behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/run_all_tests.sh | Adds suite [131] to pin --api text/JSON output and key classification facts. |
| src/main.c | Wires --api [--json] into the CLI help and dispatch. |
| src/lint.c | Refactors lib dir discovery and implements the eigs_api_dump surface index. |
| src/eigenscript.h | Declares eigs_api_dump in the public header. |
| docs/llms.txt | Points name-resolution workflows at eigenscript --api --json. |
| docs/BUILTINS.md | Clarifies map/filter/reduce are stdlib (lib/list.eigs), not builtins; points to --api. |
| CLAUDE.md | Routes .eigs writing guidance to docs/llms.txt and --api. |
| CHANGELOG.md | Documents the new --api feature and related discoverability fixes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /* #734: the --api surface index — builtins from the live registry, | ||
| * extensions from ext_names.h by group, lib/*.eigs public defines with | ||
| * their parameter lists. Name resolution in one call; conventions stay | ||
| * in docs/BUILTINS.md / docs/STDLIB.md. Hosted-only (lint.c). */ | ||
| int eigs_api_dump(FILE *out, int json); |
…ollow-up) eigs_api_dump stamps the surface index with the version, but the embed/ amalgamation build (build/eigenscript_all.c, no main.c) doesn't get the Makefile's -DEIGENSCRIPT_VERSION, so embed-smoke failed to compile. Add the same per-file #ifndef fallback main.c/trace.c/eigenlsp.c already use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/lint.c:1160
- In the JSON builtins array, the comma separator is keyed off the loop index
irather than whether an element was actually emitted. SinceEnv->namescan contain NULL “slot-only” entries, the first emitted builtin can occur ati > 0, producing a leading comma and invalid JSON.
fprintf(out, "{\"version\": \"%s\",\n \"builtins\": [",
EIGENSCRIPT_VERSION);
for (int i = 0; i < core->count; i++) {
char esc[600];
if (!core->names[i]) continue;
json_escape(core->names[i], esc, sizeof(esc));
fprintf(out, "%s\"%s\"", i ? ", " : "", esc);
}
…#734 follow-up) Section [131] failed under make asan-http (a net-enabled build): net_dial is a real registered builtin there, so the old `if (!env_get(core,nm))` guard moved it OUT of the extensions group and INTO the builtins list — the output changed with build flags, and the "extension net net_dial" assertion broke. The ext_names.h groups are the LANGUAGE surface, not this binary's build flags (that's the whole point of listing them separately), so --api must report a name in the same section every build. Now: extension group names are emitted unconditionally, and any ext-group name is excluded from the builtins list via api_is_ext_name (so a compiled-in extension never double-appears). Verified identical output across default and net builds; net_dial is `extension net`, never `builtin`, in both. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/run_all_tests.sh:3461
- The new [131]
--apitest pins thelist.filter(items, fn)case, but it doesn’t cover the implicit-parameter behavior that--apiis supposed to report (parser.c defaults an empty param list, includingdefine f(), to a single implicitn). Adding one assertion for a known stdlib function defined asdefine lock_new()would catch regressions (and will fail with the current--apiimplementation, which reports it as zero params).
if [ "$API_RC" = "0" ] \
&& echo "$API_OUT" | grep -q "^builtin sort_by$" \
&& echo "$API_OUT" | grep -q "^extension net net_dial$" \
&& echo "$API_OUT" | grep -q "^lib list.filter(items, fn)$" \
&& ! echo "$API_OUT" | grep -q "^builtin filter$"; then
src/lint.c:1080
--apiextracts lib function params by looking for '(' immediately after the name and then copying the raw substring until ')'. This diverges from the actual parser behavior in two concrete cases: (1) whitespace between the function name and '(' is allowed by the lexer/parser but will currently be treated as the implicit-nform, and (2) empty parensdefine f()currently record an empty params string even though parser.c defaults param_count==0 to the implicit single parametern. This makes--apimisreport a number of existing stdlib functions (e.g.lib/sync.eigshasdefine lock_new()), and also makes--api --jsonemit"params": []for them.
char params[1024];
if (*p == '(') {
p++;
size_t q = 0;
while (*p && *p != ')' && q + 1 < sizeof(params)) params[q++] = *p++;
Closes #734.
All three items, cheapest first, with item 3 as the code anchor.
1. CLAUDE.md routes to llms.txt
The "Writing
.eigscode?" line in Task-specific procedures now namesdocs/llms.txtalongside thewrite-eigenscriptskill — the repo's best AI-facing reference was previously reachable only from README.md line 424. On the overlap question the issue raises: kept as two documents with distinct jobs — the skill is the session-loaded trap list, llms.txt the shippable standalone reference — but they now cross-reference, and--api(below) removes the largest shared burden (name resolution) from both.2. BUILTINS.md names the lib HOFs
The builtins-vs-library note now says explicitly that
map,filter,reduceare NOT builtins, with both access forms (import list→list.filter of [xs, fn], orload_file of "lib/list.eigs"for bare names), and names thesort_by-IS-a-builtin contrast that made the omission actively misleading. The exact greps the issue ran (filter/map/reduce→ zero hits) now land on the answer.3.
eigenscript --api [--json]— the machine-readable surface indexThe missing third leg beside
--lint --jsonand--test --json. One call answers "does X exist, and is it builtin, extension, or lib":register_builtinsenv — never a hand list (the Name-keyed dispatch superinstruction hijacks a user-rebound 'dispatch' (silent wrong result); also fires on the parenthesized #355 form #459 lesson: the old hand-copied array drifted ~120 names behind the binary).gfx/http/http_request/db/model/net): fromext_names.h— the surface of the LANGUAGE, independent of this binary's build flags, so an agent learns the name exists and that it may needmake http/make gfx.lib/*.eigswith its parameter list as written (defaults included; the no-paren form reports its implicitn), scraped from the same candidate directories the import resolver searches — shared helperlib_candidate_dirs, refactored out of the W021 table builder so the two can't drift apart.Text mode is line-greppable:
--jsonis the machine form; conventions stay in BUILTINS.md/STDLIB.md — this is for name resolution, per the issue's "resolve a name in one call instead of a text search".The scratch env is freed explicitly (LSan can't trace tagged slots — same rule as
builtin_name_env_free).Tests
Suite [131]: pins the three kinds, the params capture, the
filter/sort_bysplit that misled both of the issue's test agents, and (python3-gated) that--jsonparses and carries the same facts.--helpdocuments the flag; llms.txt's helper section points at it.Validation
detect_leaks=1suite 3366/3366, leak tally 0 (the scratch-env free holds).tools/doc_drift_check.shclean.🤖 Generated with Claude Code