Skip to content

Commit 008a6ba

Browse files
committed
feat(prompt,readme): load project-instruction file + corrected feature list
- buildProjectFilesAddendum: looks for AGENTS.md → CLAUDE.md → CODEX.md → .cursorrules at the project root (first match wins), injects its content into the system prompt under a "Project instructions" header. 64KB cap with a truncation notice. createAgent layers this between the static system prompt and the MEMORY.md addendum. - README rewritten: - Honest feature list (no more demoscene boot screen — that was Go v1 only; project awareness is now actually wired up) - Codebase.design free tier explained as 10-turn taste, not unlimited - Paid account section explains what the subscription unlocks - 95 lines total
1 parent 60fa004 commit 008a6ba

5 files changed

Lines changed: 155 additions & 13 deletions

File tree

README.md

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@
1515
</p>
1616

1717
<p align="center">
18-
<a href="https://github.com/earendil-works/pi">
19-
<img src="https://pi.dev/logo-auto.svg" alt="Powered by pi" width="20" align="absmiddle" />
20-
&nbsp;<strong>Powered by <a href="https://github.com/earendil-works/pi">pi</a></strong>
21-
</a>
22-
&nbsp;·&nbsp;
18+
<strong>Powered by <a href="https://github.com/earendil-works/pi">pi</a></strong> &nbsp;·&nbsp;
2319
<em>The agent loop, provider adapters, and session protocol come from <a href="https://github.com/earendil-works/pi">pi-mono</a>.</em>
2420
</p>
2521

@@ -35,13 +31,18 @@ ANTHROPIC_API_KEY=sk-ant-... codebase
3531
OPENAI_API_KEY=sk-... codebase
3632
```
3733

38-
**Or sign in once via [codebase.design](https://codebase.design)** and try open-weight models instantly with no key setup — MiniMax, Qwen, Llama, and others routed through our inference proxy. One auth, every model.
34+
**Sign in to [codebase.design](https://codebase.design) and skip key wrangling.** One auth, every model.
35+
36+
- **Free tier** — try open-weight models (MiniMax, Qwen, Llama, etc.) with a **10-turn taste** so you can kick the tires before paying anyone for anything. No API keys to set up.
37+
- **Paid account** — uncaps the open-weights, adds the frontier models (Claude Opus / Sonnet, GPT-5, …), higher rate limits, and longer context windows. One subscription replaces N provider bills.
3938

4039
```sh
4140
codebase auth login
4241
codebase
4342
```
4443

44+
Switch between models any time with `/model` (interactive picker) or `/model <id>`.
45+
4546
## Install
4647

4748
Requires **Node.js ≥ 20**.
@@ -67,28 +68,42 @@ codebase
6768
Type. Hit enter. The agent reads files, runs tests, edits code, and shows you what it did. Slash `/help` for the rest.
6869

6970
A few things worth knowing:
70-
- `/model` — pick a model interactively (live list of what your account can hit)
71+
72+
- `/model` — pick a model interactively, live list of what your account can hit
7173
- `/plan` — Q&A before the agent touches anything
7274
- `!cmd` — run a shell command without spending a turn
7375
- `@path` — pin a file into the next prompt
7476
- `\<Enter>` — multi-line input
77+
- **Type while the agent is working** — your prompt queues, fires when the current turn ends (`Ctrl-C` while busy clears the queue along with the turn)
7578
- **Ctrl-C** stops the current turn, twice fast exits
7679

80+
## What it does
81+
82+
- **Streaming responses.** Real-time token output, coalesced to 60 fps so the TUI doesn't thrash.
83+
- **Multi-turn agentic loop.** Tool call → result → next turn, automatically.
84+
- **Parallel tool execution.** Read-only tools (grep, glob, read, git status…) run concurrently within a turn.
85+
- **Project awareness.** Auto-loads `AGENTS.md`, `CLAUDE.md`, `CODEX.md`, or `.cursorrules` from the project root into the system prompt.
86+
- **Conversation compaction.** Long sessions automatically summarize older turns to stay under the context window.
87+
- **Session persistence.** Auto-resume from where you left off; `--new` for a clean slate.
88+
- **Plan mode.** A cheap-model Q&A pass surfaces the right plan before the expensive agent starts editing.
89+
- **Intent routing.** Chit-chat doesn't burn a full agent turn; complex asks roll into plan mode automatically.
90+
- **Subagent dispatch.** Spawn isolated research agents that keep their tool noise out of your main context.
91+
7792
## Builder-focused defaults
7893

7994
- **Any LLM**, not just Anthropic. Provider choice is config.
80-
- **45+ tools** that all live behind one small interface — adding one is mechanical.
95+
- **45+ tools** behind one small interface — adding one is mechanical.
8196
- **Effect-based permissions** instead of tool-name allowlists.
82-
- **Single immutable state** driven by a typed reducer — the whole UI is one render of one value.
97+
- **Single immutable state** driven by a typed reducer — the UI is one render of one value.
8398
- **Multi-process safe OAuth** with lockfile-coordinated token refresh — run 10 instances of codebase at once and they share a single refresh per hour.
84-
- **Plain `npm i -g`**, single binary, no bundler lock-in.
99+
- **Plain `npm i -g`**, no bundler lock-in.
85100

86101
## More
87102

88103
- [`.settings/`](.settings/) — orientation: tenets, architecture, extending, testing
89104
- [`CLAUDE.md`](CLAUDE.md) — quick reference for AI agents working in this repo
90105
- [`docs/MIGRATION_v1_to_v2.md`](docs/MIGRATION_v1_to_v2.md) — upgrading from the Go v1 binary
91-
- [`/help` in the CLI](#) — every slash command and shortcut
106+
- `/help` inside the CLI — every slash command and shortcut
92107

93108
## License
94109

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codebase-cli",
3-
"version": "2.0.0-pre.51",
3+
"version": "2.0.0-pre.52",
44
"description": "Codebase CLI — a TypeScript coding agent on the pi-mono runtime. OAuth-aware, any LLM provider, single install.",
55
"keywords": [
66
"ai",

src/agent/agent.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { TaskStore } from "../tools/task-store.js";
2222
import type { ToolContext } from "../tools/types.js";
2323
import { UserQueryStore } from "../user-queries/store.js";
2424
import { type ResolvedConfig, resolveConfig } from "./config.js";
25+
import { buildProjectFilesAddendum } from "./project-files.js";
2526
import { buildSystemPrompt } from "./system-prompt.js";
2627

2728
const WRITE_TOOL_NAMES: ReadonlySet<string> = new Set(["write_file", "edit_file", "multi_edit", "notebook_edit"]);
@@ -185,7 +186,11 @@ export function createAgent(opts: CreateAgentOptions = {}): AgentBundle {
185186

186187
// MEMORY.md gets concatenated onto the system prompt at agent creation.
187188
// Reload-after-save is a Phase 11 polish item.
188-
const fullSystemPrompt = systemPrompt + buildMemoryAddendum(memory);
189+
// Project-instruction file (first of AGENTS.md / CLAUDE.md / CODEX.md /
190+
// .cursorrules) gets pinned to the prompt so the agent sees the
191+
// project's conventions on every turn. Memory addendum is appended
192+
// after — it's the user's accumulated long-term notes.
193+
const fullSystemPrompt = systemPrompt + buildProjectFilesAddendum(cwd) + buildMemoryAddendum(memory);
189194

190195
const agent = new Agent({
191196
initialState: {

src/agent/project-files.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import { join } from "node:path";
4+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
5+
import { buildProjectFilesAddendum } from "./project-files.js";
6+
7+
describe("buildProjectFilesAddendum", () => {
8+
let cwd: string;
9+
10+
beforeEach(() => {
11+
cwd = mkdtempSync(join(tmpdir(), "project-files-"));
12+
});
13+
14+
afterEach(() => {
15+
rmSync(cwd, { recursive: true, force: true });
16+
});
17+
18+
it("returns empty string when no recognized file exists", () => {
19+
expect(buildProjectFilesAddendum(cwd)).toBe("");
20+
});
21+
22+
it("loads CLAUDE.md when present", () => {
23+
writeFileSync(join(cwd, "CLAUDE.md"), "# Project Rules\n\nNo cowboy coding.");
24+
const out = buildProjectFilesAddendum(cwd);
25+
expect(out).toContain("# Project instructions (CLAUDE.md)");
26+
expect(out).toContain("No cowboy coding");
27+
});
28+
29+
it("loads AGENTS.md when present and prefers it over CLAUDE.md (first match wins)", () => {
30+
writeFileSync(join(cwd, "AGENTS.md"), "AGENTS RULES");
31+
writeFileSync(join(cwd, "CLAUDE.md"), "CLAUDE RULES");
32+
const out = buildProjectFilesAddendum(cwd);
33+
expect(out).toContain("# Project instructions (AGENTS.md)");
34+
expect(out).toContain("AGENTS RULES");
35+
expect(out).not.toContain("CLAUDE RULES");
36+
});
37+
38+
it("loads CODEX.md when neither AGENTS.md nor CLAUDE.md exists", () => {
39+
writeFileSync(join(cwd, "CODEX.md"), "CODEX RULES");
40+
const out = buildProjectFilesAddendum(cwd);
41+
expect(out).toContain("# Project instructions (CODEX.md)");
42+
expect(out).toContain("CODEX RULES");
43+
});
44+
45+
it("loads .cursorrules as a last resort", () => {
46+
writeFileSync(join(cwd, ".cursorrules"), "CURSOR RULES");
47+
const out = buildProjectFilesAddendum(cwd);
48+
expect(out).toContain("# Project instructions (.cursorrules)");
49+
expect(out).toContain("CURSOR RULES");
50+
});
51+
52+
it("truncates content past the byte cap with a notice", () => {
53+
const huge = "x".repeat(70 * 1024); // 70KB > 64KB cap
54+
writeFileSync(join(cwd, "CLAUDE.md"), huge);
55+
const out = buildProjectFilesAddendum(cwd);
56+
expect(out).toContain("…truncated");
57+
expect(out).toContain("CLAUDE.md");
58+
expect(out.length).toBeLessThan(huge.length);
59+
});
60+
61+
it("ignores a directory named CLAUDE.md (not a regular file)", () => {
62+
mkdirSync(join(cwd, "CLAUDE.md"));
63+
expect(buildProjectFilesAddendum(cwd)).toBe("");
64+
});
65+
66+
it("trims surrounding whitespace from the file content", () => {
67+
writeFileSync(join(cwd, "CLAUDE.md"), "\n\n rule one \n\n");
68+
const out = buildProjectFilesAddendum(cwd);
69+
expect(out).toContain("rule one");
70+
// Section structure intact, no leading/trailing spaces on the content body.
71+
expect(out).toMatch(/\n\nrule one\n\n*$/);
72+
});
73+
});

src/agent/project-files.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { readFileSync, statSync } from "node:fs";
2+
import { join } from "node:path";
3+
4+
/**
5+
* Project-instruction files we look for at the cwd root. The first
6+
* present file wins to avoid duplicate injection — a project that has
7+
* both CLAUDE.md (for one IDE) and AGENTS.md (for another) shouldn't
8+
* concatenate both into the prompt.
9+
*/
10+
const RECOGNIZED_FILES = ["AGENTS.md", "CLAUDE.md", "CODEX.md", ".cursorrules"] as const;
11+
12+
/** Hard cap on the injected content. Anything beyond this is truncated
13+
* with a notice. Keeps a runaway CLAUDE.md from blowing the prompt. */
14+
const MAX_BYTES = 64 * 1024;
15+
16+
/**
17+
* Build the project-instructions system-prompt addendum. Reads the
18+
* first recognized file at the cwd root (AGENTS.md / CLAUDE.md /
19+
* CODEX.md / .cursorrules) and returns it wrapped in a labeled
20+
* section. Returns "" when no file is present.
21+
*/
22+
export function buildProjectFilesAddendum(cwd: string): string {
23+
for (const name of RECOGNIZED_FILES) {
24+
const path = join(cwd, name);
25+
try {
26+
const stat = statSync(path);
27+
if (!stat.isFile()) continue;
28+
let content = readFileSync(path, "utf8");
29+
let truncated = false;
30+
if (Buffer.byteLength(content, "utf8") > MAX_BYTES) {
31+
// Slice by bytes, then trim back to a char boundary by
32+
// re-decoding. utf8 truncation can split a multibyte
33+
// sequence; slicing the string and re-encoding is safer
34+
// than buffer math here.
35+
content = content.slice(0, MAX_BYTES);
36+
while (Buffer.byteLength(content, "utf8") > MAX_BYTES) {
37+
content = content.slice(0, -1);
38+
}
39+
truncated = true;
40+
}
41+
const header = `\n\n# Project instructions (${name})\n\n`;
42+
const footer = truncated ? `\n\n(…truncated; full file at ${path})` : "";
43+
return `${header}${content.trim()}${footer}\n`;
44+
} catch {
45+
// File missing or unreadable — move on to the next candidate.
46+
}
47+
}
48+
return "";
49+
}

0 commit comments

Comments
 (0)