Skip to content
RadKaiPublic

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

selfdoc: write it once, humans and AI agents both understand your project

status: early development license: MIT node >= 18">https://img.shields.io/badge/node-%E2%89%A518-339933"> written in TypeScript no LLM SDK required

selfdoc reads your codebase, asks an LLM of your choice to describe what the project does, and turns the answer into two things from a single source of truth:

  • 🧑‍💻 For you: an interactive index.html summary you can search, share, or print to PDF as a portfolio page.
  • 🤖 For AI agents: an AGENTS.md with the project's rules, conventions and no-go zones, so coding agents stop guessing.

You never have to open the README to remember how your own project works. Neither does your AI.

Your repo is scanned, your LLM describes it, the result is saved as manifest.json and rendered to index.html and AGENTS.md

The problem

You build a finance CMS. Six months later:

  • You need a portfolio page, and the README is stale.
  • Nobody remembers that "transactions are never deleted, only voided".
  • An AI agent joins the repo, rediscovers everything from scratch, and breaks that rule.

The same knowledge is needed twice, once by humans and once by agents. It gets written twice, badly, and drifts apart. selfdoc writes it once.

Quick start

selfdoc is not on npm yet. Until it is published, build it locally (see Develop).

1. Tell selfdoc which LLM to use. Create selfdoc.config.mjs in your project root. It exports one function that takes a prompt and returns the model's reply text:

export default {
  llm: async ({ system, prompt }) => {
    // call any model you like and return its reply as a string
  },
};

A ready-to-copy Anthropic example is in examples/selfdoc.config.mjs.

2. Preview what would be sent, then run it:

npx selfdoc init --dry-run   # lists the files that would be sent to the LLM
npx selfdoc init             # scan → LLM → manifest → index.html + AGENTS.md

3. Open .selfdoc/index.html. To get a PDF, use your browser's Print → Save as PDF. No headless browser needed.

4. Keep it current. After you change code:

npx selfdoc update           # re-analyze, show what changed, re-render
npx selfdoc build            # re-render only, after editing the manifest by hand (no LLM call)

update fingerprints what it scans, so if nothing changed it exits without calling the LLM.

Release

One command updates the docs, writes a changelog entry, and bumps your project's version:

npx selfdoc release --dry-run    # preview the plan, write nothing
npx selfdoc release              # show the plan, ask, then write
npx selfdoc release --yes --commit --tag
Release plan
  version:  1.0.0 -> 1.1.0 (minor)
  changes:  3 commits since v1.0.0
  writes:   CHANGELOG.md, package.json, .selfdoc/manifest.json, AGENTS.md
  git:      commit "chore(release): v1.1.0", tag v1.1.0

## [1.1.0] - 2026-10-25

### Added
- Invoice export to PDF

How it decides:

  1. Changes are measured since your last git tag (or the last release selfdoc recorded): commit messages, plus what changed in the documented modules and behaviours.
  2. Your LLM writes the entry, grouped as Added / Changed / Fixed / Removed, following Keep a Changelog.
  3. The version bump is suggested by rules, not by the LLM: a breaking marker (feat!:, BREAKING CHANGE:, or the LLM flagging it) means major; a feat: commit or a new module or behaviour means minor; anything else is patch. You always see it before anything is written, and can override it with --bump major|minor|patch or --release-as 1.2.3.
  4. Files written: CHANGELOG.md (new entries go on top, your header is kept), the version in package.json, pyproject.toml, Cargo.toml or composer.json, the manifest history, and the refreshed index.html and AGENTS.md. Go has no version file, so Go projects get the changelog and tag only.
  5. Git is opt-in. --commit commits only the release files (your other staged changes are left alone), --tag adds an annotated vX.Y.Z tag. selfdoc never pushes.

Only plain MAJOR.MINOR.PATCH versions can be bumped automatically; use --release-as for anything else. Without --yes, release asks for confirmation and refuses to write in a non-interactive shell.

What you get

Generated index.html for an example Finance CMS project, showing modules and behaviours with confidence scores

Generated from examples/finance-cms.manifest.json, an illustrative example project.

  • Searchable and collapsible modules and behaviours, light and dark mode, print-friendly.
  • Confidence scores on every behaviour, so you can see what the LLM is sure about and what it is inferring.
  • Evidence for every behaviour: the files that support it, so claims are checkable.
  • Human-locked entries. Rules you write yourself are marked human and are never silently overwritten when the LLM re-analyzes.

How it works

  1. Scan. Walks the repo from the root and picks the shortest useful view: project metadata (package.json, pyproject.toml, composer.json, go.mod, Cargo.toml), existing docs, entry points, routes, schemas, and test names. Default budget is about 8k tokens (--budget to change it).
  2. Analyze. Sends that view to your LLM and asks for structured JSON. If the reply is malformed it retries once, telling the model what was wrong.
  3. Manifest. Saves .selfdoc/manifest.json. Commit it: it holds your locked entries.
  4. Render. Produces .selfdoc/index.html and updates AGENTS.md.

Works with any language, because the scanner is file-based and the LLM does the reading.

The manifest

{
  "project": { "name": "Finance CMS", "tagline": "…", "summary": "…", "stack": ["TypeScript", "PostgreSQL"] },
  "modules": [{ "name": "Ledger", "path": "src/ledger", "purpose": "…", "keyFiles": ["src/ledger/void.ts"] }],
  "behaviors": [{
    "id": "tx-never-deleted",
    "rule": "Transactions are never deleted, only voided with a reversing entry.",
    "evidence": ["src/ledger/void.ts"],
    "confidence": 0.95,
    "source": "human"          // "human" entries are locked
  }],
  "conventions": ["Money is stored as integer cents, never floats."],
  "doNotTouch": ["prisma/migrations/: append-only"]
}

AGENTS.md

selfdoc only edits the block between these markers. Everything else in your file is yours:

# AGENTS.md

Your own hand-written notes stay here.

<!-- selfdoc:start -->
…generated by selfdoc…
<!-- selfdoc:end -->

AGENTS.md is a plain-Markdown convention read by several AI coding tools. Some tools use a different filename; for example, Claude Code reads CLAUDE.md, which can simply contain @AGENTS.md. Check your tool's documentation for what it picks up automatically.

Bring your own LLM

selfdoc has no LLM SDK dependency. Any provider works as long as you can write a function that returns text. Many providers expose an OpenAI-compatible endpoint, so one pattern covers a lot of them:

export default {
  llm: async ({ system, prompt }) => {
    const res = await fetch(`${process.env.LLM_BASE_URL}/chat/completions`, {
      method: "POST",
      headers: { "content-type": "application/json", authorization: `Bearer ${process.env.LLM_API_KEY}` },
      body: JSON.stringify({
        model: process.env.LLM_MODEL,
        messages: [{ role: "system", content: system }, { role: "user", content: prompt }],
      }),
    });
    return (await res.json()).choices[0].message.content;
  },
};

Change the base URL and model to switch provider, including local models. selfdoc needs a model that can follow "reply with JSON only"; very small models may struggle.

You can also use it as a library:

import { init, update, release } from "selfdoc";

const llm = async ({ system, prompt }) => callMyModel(system, prompt);

await init({ root: process.cwd(), llm });
await update({ root: process.cwd(), llm });
const { plan } = await release({ root: process.cwd(), llm, dryRun: true });

Privacy and safety

  • --dry-run shows exactly which files would be sent to the LLM.
  • Secrets are never read: .env* (except .env.example), private keys, and credentials files are skipped, as is everything in your .gitignore.
  • selfdoc runs locally. No server, no account, no telemetry.
  • Generated HTML escapes all manifest text.

Roadmap

  • v0.1: scan, manifest, init and build, HTML and AGENTS.md output
  • v0.2: update (re-analyze only what changed) and release (changelog entry and SemVer version bump for your project)
  • v0.3: terminal review for low-confidence items, check for CI
  • v0.4: polished print/PDF styling and themes

Develop

git clone https://github.com/RadKai/selfdoc.git && cd selfdoc
npm install
npm test          # vitest
npm run typecheck
npm run build     # tsup → dist/
node dist/cli.js init --dry-run --root /path/to/some/project

License

MIT © 2026 radkai.studio

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages