Skip to content

cli+docs: --api surface index; route CLAUDE.md to llms.txt; name the lib HOFs in BUILTINS.md (#734) - #803

Merged
InauguralPhysicist merged 3 commits into
mainfrom
fix-734-api-index
Aug 2, 2026
Merged

cli+docs: --api surface index; route CLAUDE.md to llms.txt; name the lib HOFs in BUILTINS.md (#734)#803
InauguralPhysicist merged 3 commits into
mainfrom
fix-734-api-index

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Closes #734.

All three items, cheapest first, with item 3 as the code anchor.

1. CLAUDE.md routes to llms.txt

The "Writing .eigs code?" line in Task-specific procedures now names docs/llms.txt alongside the write-eigenscript skill — 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, reduce are NOT builtins, with both access forms (import listlist.filter of [xs, fn], or load_file of "lib/list.eigs" for bare names), and names the sort_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 index

The missing third leg beside --lint --json and --test --json. One call answers "does X exist, and is it builtin, extension, or lib":

  • builtins (249): enumerated from a scratch register_builtins env — 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).
  • extensions (86, by group gfx/http/http_request/db/model/net): from ext_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 need make http/make gfx.
  • lib (815): every public top-level define of lib/*.eigs with its parameter list as written (defaults included; the no-paren form reports its implicit n), scraped from the same candidate directories the import resolver searches — shared helper lib_candidate_dirs, refactored out of the W021 table builder so the two can't drift apart.

Text mode is line-greppable:

builtin sort_by
extension net net_dial
lib list.filter(items, fn)

--json is 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_by split that misled both of the issue's test agents, and (python3-gated) that --json parses and carries the same facts. --help documents the flag; llms.txt's helper section points at it.

Validation

  • Release suite 3362/3362; ASan+UBSan detect_leaks=1 suite 3366/3366, leak tally 0 (the scratch-env free holds).
  • tools/doc_drift_check.sh clean.

🤖 Generated with Claude Code

…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>
Copilot AI review requested due to automatic review settings August 2, 2026 06:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/eigenscript.h
Comment on lines +1342 to +1346
/* #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>
Copilot AI review requested due to automatic review settings August 2, 2026 06:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 i rather than whether an element was actually emitted. Since Env->names can contain NULL “slot-only” entries, the first emitted builtin can occur at i > 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>
Copilot AI review requested due to automatic review settings August 2, 2026 07:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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] --api test pins the list.filter(items, fn) case, but it doesn’t cover the implicit-parameter behavior that --api is supposed to report (parser.c defaults an empty param list, including define f(), to a single implicit n). Adding one assertion for a known stdlib function defined as define lock_new() would catch regressions (and will fail with the current --api implementation, 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

  • --api extracts 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-n form, and (2) empty parens define f() currently record an empty params string even though parser.c defaults param_count==0 to the implicit single parameter n. This makes --api misreport a number of existing stdlib functions (e.g. lib/sync.eigs has define lock_new()), and also makes --api --json emit "params": [] for them.
        char params[1024];
        if (*p == '(') {
            p++;
            size_t q = 0;
            while (*p && *p != ')' && q + 1 < sizeof(params)) params[q++] = *p++;

@InauguralPhysicist
InauguralPhysicist merged commit 9544bcd into main Aug 2, 2026
19 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the fix-734-api-index branch August 2, 2026 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI discoverability: llms.txt is invisible to agents working in the repo, BUILTINS.md omits map/filter/reduce, and there is no machine-readable API index

2 participants