lingoc
v0.5.0
Published
Program in English. lingoc compiles .prompt files into source code via an Orchestration AI agent.
Maintainers
Readme
lingoc
Program in English. lingoc is a CLI that compiles .prompt files into real source files using an Orchestration AI agent. Each .prompt file is a plain-English, declarative description of what a single source file should be; lingoc runs it through an LLM and materialises the generated code into a dist/ folder you can run directly.
hello.js.prompt ──lingoc──▶ dist/hello.js ──▶ node dist/hello.jsHow it works
- You write
hello.js.promptdescribing, in English, whathello.jsshould do. - lingoc sends that description to a single-layer OAI "compiler" agent.
- The agent returns the source code; lingoc extracts it (even if the model wraps it in Markdown fences) and writes
dist/hello.js. - Results are cached by content hash in a global folder, so unchanged prompts never re-run inference (saving tokens).
The rule is simple: strip the trailing .prompt. The extension in front of .prompt becomes the real file extension and tells the agent which language to target.
| Prompt file | Output | Target language hint |
|---|---|---|
| hello.js.prompt | dist/hello.js | js |
| api/server.py.prompt | dist/api/server.py | py |
| notes.md.prompt | dist/notes.md | md |
Prerequisites
- An Orchestration AI account.
- A registered application in the OAI console (this grants you a client id and an access key).
- Grant that application access to call agents in your workspace.
The console hands you a client id in appId:userId (XXX:YYY) form and an access key (the OAuth client secret, also used as the engine bearer token).
Install
npm install -g lingocUsage
Run lingoc in a project directory containing .prompt files:
lingocOn first run it will:
- Ask for your OAI client id and access key (or read
OAI_CLIENT_ID/OAI_ACCESS_KEYfrom the environment). - Provision — if missing — a dedicated workspace, orchestration, agent, and single compiler layer.
- Ask which LLM to use (or, if already set up, whether you want to change it).
- Add
dist/to your.gitignore. - Compile all prompts, then drop you into an interactive session.
Interactive commands
| Command | Action |
|---|---|
| r | Recompile all prompts (uses cache) |
| R | Force recompile all prompts (ignore cache) |
| <name> | Recompile a single prompt by name |
| t, test | Run tests and auto-fix failures (append a name to scope it, e.g. t greet) |
| l, list | List prompt -> dist mappings |
| a, advise | On a test failure, suggest edits to your prompts (advisor; nothing is changed) |
| m, model | Change the compiler LLM |
| w, watch | Toggle watch mode (auto-recompile on save) |
| c, clean | Clear the global compile cache |
| h, help | Show help |
| q, quit | Exit |
Non-interactive commands
lingoc init # provision + set up .gitignore, then exit
lingoc compile # compile all prompts once (uses cache), then exit
lingoc compile --force # ignore the cache and re-infer everything
lingoc test # compile, run tests, and auto-fix failures via feedback
lingoc test --no-fix # run tests once without the feedback loop
lingoc test --only greet # scope to a single prompt
lingoc test --advise # on failure, also print advisor prompt-edit suggestions
lingoc advise # run tests once and, on failure, suggest prompt edits
lingoc clean # clear the global cache and remove dist/
lingoc logout # forget stored credentialsTesting & the feedback loop
lingoc can verify the code it generates against tests you write, and automatically ask the LLM to fix failures.
Writing tests
Put tests in a top-level
test/folder, named*.test.ts.Tests import the compiled artifacts from
dist/(not the.promptfiles).A test file maps to a prompt by basename:
test/greet.test.tscoversgreet.js.prompt(i.e.dist/greet.js).To override the mapping, add one or more directives anywhere in the test file:
// @lingoc-covers api/server.py.prompt
Module-style test (test/greet.test.ts):
import { it, expect } from "vitest";
import { greet } from "../dist/greet.js";
it("greets by name", () => {
expect(greet("Sam")).toBe("Hello, Sam");
});Process-style test (for scripts/CLIs) using the bundled helper:
import { it, expect } from "vitest";
import { runDist } from "lingoc/test";
it("prints a greeting", async () => {
const { stdout, code } = await runDist("greet.js");
expect(code).toBe(0);
expect(stdout).toContain("Hello");
});runDist works with any language, not just Node. The launcher is auto-detected
from the file extension (.js/.mjs/.cjs → node, .py → python3, .rb →
ruby, .sh → bash, .pl → perl, .php → php, .lua → lua). You can override
it:
await runDist("server.py"); // auto-detected → python3
await runDist("script", { interpreter: "bash" }); // explicit interpreter
await runDist("mytool", { interpreter: null }); // run directly (binary or
// shebang script; made
// executable automatically)runDist also accepts args, input (stdin), cwd, env, and timeoutMs.
For full control, runProgram(command, args, options) spawns any command.
How the loop works
- lingoc compiles the prompt into
dist/. - It runs the covering tests (via vitest).
- On failure, it sends a compact failure report back to the same OAI agent conversation (using an OAI session id, so the agent remembers the spec and the code it already wrote) and asks for a corrected file.
- It rewrites
dist/and re-runs the tests, up to 3 attempts by default.
Notes:
- Only source that passes its tests is cached — failing attempts are never cached, so a later run always retries.
- The test source is not sent to the LLM (so it fixes the implementation rather than hardcoding to the tests).
lingoc test # all prompts with tests, with auto-fix
lingoc test --only greet # scope to one prompt
lingoc test --no-fix # just report, no fixing
lingoc test -n 5 # allow up to 5 fix attemptsThe advisor (human-in-the-loop)
The auto-fix loop and compile-attribution can only take the generated code so
far. When a build or test failure is really caused by an ambiguous or incomplete
.prompt, lingoc can ask a second agent — the advisor — to suggest how to edit
your prompts so the error won't happen again.
The advisor is provisioned automatically alongside the compiler (its own agent and layer, reusing your chosen model). Given a failure report it:
- finds the source files named in the error,
- maps them back to the
.promptfiles that generated them, - reads those prompts, their generated source, and the shared context, and
- returns plain-English suggestions for editing the prompts.
It only suggests — it never edits your prompts or writes code. You stay in control.
lingoc advise # run tests once; on failure, print prompt-edit suggestions
lingoc test --advise # run the normal test/fix loop, then advise if still failingIn the interactive session, use a (or advise), optionally scoped to one
prompt (e.g. a greet). Only the files referenced in the error are inspected.
Multi-file projects
Most .prompt files compile in isolation. But real projects have files that
reference each other — a controller calls a service, a service uses a
repository and a shared model. lingoc supports these with three cooperating
mechanisms. See example/petstore for a full Spring Boot
REST API compiled from prompts (it also serves interactive API docs — once
running, open http://localhost:8080/swagger-ui.html).
1. Shared context (context/*.md)
Put cross-file contracts — package names, domain schemas, exact JSON field
names, REST conventions — in markdown files under a top-level context/
folder. Every file's compile prompt is prefixed with this shared context, so
independently generated files agree.
context/
00-conventions.md # package name, framework, layout
10-domain.md # the authoritative entity schema + field names
20-api.md # endpoint contract, error shapesAll context/**/*.md are concatenated in sorted path order. Editing shared
context invalidates the cache for every file (its key includes the context),
because the agreed contract changed.
2. Compile order (lingoc.order)
All files in a run are generated through one OAI session. Because session
memory is sequential, a file generated later sees the actual code of earlier
files. A top-level lingoc.order file lets you declare that order so
lower-level modules are generated before the higher-level modules that depend
on them:
# folders (expand to their prompts, sorted) or individual prompts (to pin a
# file before others in the SAME folder). Lines starting with # are comments.
src/main/java/com/example/petstore/model/PetStatus.java.prompt
src/main/java/com/example/petstore/model/Pet.java.prompt
src/main/java/com/example/petstore/repository
src/main/java/com/example/petstore/service
src/main/java/com/example/petstore/web
pom.xml.promptAnything not listed is compiled afterwards in the default sorted order. Cached
files are written straight to dist/ and don't consume the session, so a fully
cached project does no inference.
3. Build gate with compile-error auto-fix
Pass a build command to lingoc compile to verify the generated files actually
agree at the compiler level, and auto-fix failures:
lingoc compile --build "mvn -q -B compile"On a build failure, lingoc parses the compiler's file diagnostics, maps each
offending file back to its .prompt, and regenerates that file — re-seeding a
session with the file's predecessors (from dist/) and feeding the compiler
error as context. It retries up to -n rounds (default 3).
Caveats (important)
- Compile-error attribution is imperfect.
javacsometimes blames the caller when the callee's signature is wrong, so lingoc may regenerate the file the compiler named rather than the true culprit. The shared context and shared session mitigate this (both files derive from the same contract), but don't eliminate it. - Diagnostics that map to no known prompt are reported and ignored — lingoc won't guess.
- Acceptance tests are a human-gated reporting gate, not an auto-fixer. A
test file that doesn't map to a single prompt (e.g. an end-to-end
test/petstore.test.ts) is treated as a project-level acceptance test: it is run once and reported (and counts toward overall pass/fail), but a failure does not drive regeneration. Only compile errors (which name a file) drive automatic regeneration. Generate the project first (lingoc compile --build …), then runlingoc testfor the acceptance suite. - Token budget grows with the project. The shared session accumulates the source of every generated file. This is fine for a small vertical slice; a large project would need pruning (a future enhancement).
- Bidirectional agreements rely on shared context. Sequential session memory helps a dependency chain, but two files that must agree even though neither strictly precedes the other (e.g. a DTO shape and a service return type) depend on the shared-context contract to line up.
- The build/acceptance tools must be installed. The petstore example needs a
JDK and Maven; its acceptance tests skip themselves with a warning when those
aren't on
PATH.
| Variable | Description |
|---|---|
| OAI_CLIENT_ID | Client id in appId:userId form |
| OAI_ACCESS_KEY | Access key / OAuth client secret |
Stored credentials live in a per-user config file (via configstore). The compile cache lives in your OS user cache directory (via env-paths), outside any project, so it is shared across projects and survives deleting dist/.
Cache invalidation
A cache entry's key is the SHA-256 of promptContent + targetExtension + llmId + promptVersion. Changing the prompt text, the target language, or the selected model automatically produces a new key, so stale output is never served. lingoc clean clears everything.
Example
Create greet.js.prompt:
A Node.js script that prints a friendly greeting including the current time.
Use only built-in modules.Then:
lingoc compile
node dist/greet.jsDevelopment
npm install # installs deps
npm run build # tsc -> dist/
npm test # vitest
npm run dev # tsc --watchLicense
MIT
