@saikusal/docsync
v0.1.2
Published
Keeps a repository's README in sync with its code: env vars, API routes, setup, tech stack and more.
Maintainers
Readme
docsync
Keep your README in sync with your code, automatically.
docsync is a small command line tool for JavaScript and TypeScript projects. It reads the facts that already exist in your code (environment variables, API routes, npm scripts, dependencies, license, folder layout) and writes them into your README. In CI it fails the build when a code change makes the README wrong, so out of date documentation can no longer reach your main branch.
- It only edits the parts of the README you mark for it. Your own writing is never touched.
- It never guesses. Anything it cannot determine is written as
Not Found. - It never reads your secrets.
.envfiles are never opened, and only variable names are documented. - It needs no AI model, no database, no server and no paid service.
Contents
- The problem
- What docsync does
- Quick start
- Using docsync in your own project
- Commands, options and exit codes
- The sections and their rules
- Configuration
- Why GitHub Actions
- Using other CI systems or git hooks
- Architecture
- Technical details
- Security model
- Known limitations
- Roadmap
- How this project was built
- Development
- Project facts (generated by docsync)
1. The problem
Almost every project has a README that explains what the project is, how to install it, which settings it needs and which API it offers. The code changes every day. The README usually does not.
A typical story:
- On Monday a developer adds a new required setting,
STRIPE_API_KEY, and updates the code but not the README. - On Friday a new teammate follows the README. The app crashes with "missing STRIPE_API_KEY", and an hour is lost.
The same happens with new API endpoints, renamed npm scripts, a raised Node.js version and new dependencies. After a few months nobody trusts the README, so people stop reading it and ask colleagues instead.
Why it stays broken:
- Nobody owns it. Updating the docs is not part of anyone's task.
- Reviewers cannot see it. A pull request that adds an environment variable looks correct. Nothing shows that the README is now wrong.
- It is tedious. Copying 15 variables and 20 routes into tables by hand is boring and error prone.
2. What docsync does
Most facts in a README already exist in the code. docsync extracts them and keeps the README sections up to date.
| README section | Where docsync gets the facts |
| --- | --- |
| Environment variables, and whether each one is required | process.env.X in the code, plus the names in .env.example |
| API endpoints | Express route definitions, including routers mounted in other files |
| Setup: Node.js version, install command, scripts | package.json and the lockfile |
| Tech stack and dependency versions | package.json and package-lock.json |
| Overview: name, version, license, latest release | package.json, the LICENSE file and GitHub |
| Project structure | The top level folders |
Example
Given this code:
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is missing');
app.listen(process.env.PORT || 3000);
app.use('/api', ordersRouter); // in server.js
ordersRouter.post('/orders', createOrder); // in routes/orders.jsdocsync writes:
| Variable | Required | Used in |
| --- | --- | --- |
| DATABASE_URL | Yes | src/server.js |
| PORT | No | src/server.js |
| Method | Path | Source |
| --- | --- | --- |
| POST | /api/orders | src/routes/orders.js |
It understands that PORT is optional because it has a fallback (|| 3000), that DATABASE_URL is required because the
code stops without it, and that the orders router is mounted under /api in a different file.
Three commands
| Command | What it means |
| --- | --- |
| docsync init | Mark the places in my README that docsync may manage. |
| docsync sync | Update those places from the code. |
| docsync check | Tell me if my README is still correct. Changes nothing. |
The markers are HTML comments, so they are invisible when the README is displayed:
## Environment variables
<!-- docsync:start env-vars -->
(docsync owns only what is between these two lines)
<!-- docsync:end env-vars -->Everything outside the markers stays exactly as you wrote it, byte for byte.
3. Quick start
You need Node.js 22.12 or newer. Run these commands in the root folder of your project:
npx @saikusal/docsync init # shows which sections it will add, then asks
npx @saikusal/docsync sync # fills the sections from your code
npx @saikusal/docsync check # exit code 0 means the README is up to datedocsync is published on npm as @saikusal/docsync. The command it installs is
called docsync.
4. Using docsync in your own project
Step 1: Install
Choose one option.
Option A: no installation. Run it with npx @saikusal/docsync as shown in the quick start. This is the easiest way and
works in CI too.
Option B: install once on your machine.
npm install --global @saikusal/docsyncAfter this the docsync command works in every folder.
Option C: pin a version in your project. Add it as a development dependency so everyone on the team uses the same version:
npm install --save-dev @saikusal/docsyncThen add scripts to your package.json:
{
"scripts": {
"docs:sync": "docsync sync",
"docs:check": "docsync check"
}
}Step 2: Add the markers
docsync initdocsync lists the sections it will add and asks for confirmation. It adds each section at the end of the README with a
heading. You can move the blocks and rename the headings afterwards; only the marker lines must stay. If you do not want a
section, delete its block or leave it out in the configuration (see section 7). If there is no README, init creates one.
Step 3: Fill the sections
docsync syncRead the result, then commit the README. From now on, run docsync sync whenever you change code that affects the docs.
Running it twice in a row changes nothing the second time.
Step 4: Protect the README in CI
Create .github/workflows/docs.yml in your project:
name: docs
on: pull_request
permissions:
contents: read
jobs:
docs-check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npx --yes @saikusal/docsync checkNow every pull request is checked. When someone changes the code without updating the docs, the job fails and shows exactly
which lines are missing, both in the job log and in the job summary. The fix is always the same: run docsync sync and commit.
Step 5 (optional): Check a repository on GitHub without cloning it
docsync check --repo your-name/your-repo
docsync sync --repo your-name/your-repo --out README.new.mdIn this mode docsync reads the repository through the GitHub API. It never writes to GitHub; sync prints the updated
README or writes it to the file given with --out.
5. Commands, options and exit codes
| Command | What it does |
| --- | --- |
| docsync init | Adds markers for the sections that are missing, or creates a README. Asks first; use --yes in scripts. |
| docsync sync | Rewrites the sections that are out of date. Text outside the markers is not changed. |
| docsync check | Changes nothing. Prints a diff for every section that is out of date. |
| Option | Meaning |
| --- | --- |
| --path <dir> | The project folder. Default: the current folder. |
| --repo <owner/repo> | Read a GitHub repository through the API instead of a local folder. |
| --ref <ref> | Branch, tag or commit to read with --repo. Default: the default branch. |
| --readme <file> | README path relative to the project root. Default: README.md. |
| --sections <list> | Comma separated list of sections to process. |
| --config <file> | Configuration file. Default: docsync.config.json in the project root. |
| --out <file> | Write the README to this file instead of changing it in place. |
| --yes | init only: do not ask for confirmation. |
| --debug | Show extra details and stack traces. |
Run docsync <command> --help for the full help text.
| Exit code | Meaning |
| --- | --- |
| 0 | Success. For check: the README is up to date. |
| 1 | check found sections that are out of date. |
| 2 | A problem with the command: wrong option, invalid config, missing README or broken markers. |
| 3 | A problem with the source: empty repository, GitHub API error, or a file that cannot be read. |
Separate codes for "docs are out of date" and "the tool failed" let a CI job report the right message.
6. The sections and their rules
| Section id | Content | Rules |
| --- | --- | --- |
| overview | Name, description, version, license, default branch, topics, latest release | GitHub data is preferred, then package.json, then the LICENSE file (MIT, Apache 2.0, GPL, ISC, MPL and others are recognised). |
| tech-stack | Node.js version range, language, every dependency with its version | Exact versions come from package-lock.json when present. Runtime dependencies are listed before development dependencies. |
| setup | Prerequisites, install command, every npm script with its exact command | The install command matches the lockfile: npm, pnpm, yarn or bun. |
| env-vars | Every environment variable, whether it is required, and the files that use it | See the rule below. Values are never shown. |
| api-endpoints | Every Express route: method, full path, source file | Prefixes from app.use('/api', router) are applied, also across files. Paths built at runtime are shown as Not Found (dynamic). |
| project-structure | Top level folders and their purpose | Well known names (src, tests, docs and others) get a purpose; any other folder shows Not Found. |
When is a variable required? A variable is required when at least one place uses it without a fallback.
| Code | Result |
| --- | --- |
| process.env.DATABASE_URL | Required |
| if (!process.env.API_KEY) throw new Error() | Required (the guard proves it is needed) |
| process.env.PORT \|\| 3000 | Optional |
| process.env.LOG_LEVEL ?? 'info' | Optional |
| const { HOST = 'localhost' } = process.env | Optional |
| Only listed in .env.example | Required and Used in both show Not Found |
Test files, node_modules, build output and files listed in .gitignore are never scanned for variables or routes.
7. Configuration
Configuration is optional. Create docsync.config.json in the project root:
{
"readme": "README.md",
"sections": ["overview", "setup", "env-vars", "api-endpoints"],
"include": ["src/**"],
"exclude": ["scripts/legacy/"]
}| Key | Meaning |
| --- | --- |
| readme | Which file to manage. |
| sections | Which sections to manage. Blocks for other sections in the README are left alone. |
| include | If set, only matching files are analysed for variables and routes. Uses .gitignore style patterns. |
| exclude | Files and folders to ignore completely. |
Command line options take precedence over the file, and the file takes precedence over the defaults. Unknown keys and wrong types are reported with the name of the field.
GitHub token (optional). Set the environment variable GITHUB_TOKEN to a read only token (permission contents: read)
when you want to use --repo with private repositories, need a higher API rate limit (5000 instead of 60 requests per hour),
or want GitHub facts such as topics and the latest release in local mode. Without a token those fields show Not Found.
Run sync and check the same way, both with or both without a token, otherwise those fields will differ.
8. Why GitHub Actions
docsync on its own is a command you run by hand. The real value comes when the check runs automatically on every change. That is the job of a CI (continuous integration) system, and this project uses GitHub Actions for these reasons:
- It runs on every pull request without anyone remembering to. A forgotten docs update is caught before the merge, not months later.
- It can block the merge. With branch protection, a pull request with out of date docs cannot be merged.
- The same check for everyone. Every pull request is checked on a clean machine with the same Node.js version, so "it works on my machine" does not apply.
- The reviewer sees the result. The diff of the stale sections appears in the job log and in the job summary on the pull request page.
- No infrastructure to maintain. GitHub provides the machines. For public repositories GitHub Actions is free.
- It lives next to the code. The workflow is a file in the repository, versioned and reviewed like any other code.
- Testing on several systems. This project's own workflow runs the full test suite on Ubuntu and Windows with Node.js 22 and 24, which proves docsync works on all of them.
This repository has two CI jobs, defined in .github/workflows/ci.yml:
| Job | What it does |
| --- | --- |
| test | Runs on Ubuntu and Windows with Node.js 22 and 24 (4 combinations): install, lint, type check, all tests with coverage, and a dependency security audit. |
| docs-check | Runs docsync check on this README. The README you are reading is maintained by docsync itself, so this job proves the tool works on a real project. |
Both jobs have a time limit, use read only permissions, and never receive a token.
A note on accounts: GitHub stops all Actions jobs for an account with an unresolved billing problem, even for free public repositories. If every job fails within a few seconds with "account is locked due to a billing issue", fix the billing settings of the GitHub account and run the jobs again. Nothing in the project needs to change.
9. Using other CI systems or git hooks
docsync check only needs Node.js and returns exit code 1 when the docs are out of date, so any CI system can run it.
GitLab CI (.gitlab-ci.yml):
docs-check:
image: node:22
script:
- npx --yes @saikusal/docsync checkJenkins, Azure Pipelines, CircleCI and others: add a step that runs npx --yes @saikusal/docsync check in an
environment with Node.js 22.12 or newer.
A local git hook that refuses commits with stale docs, for example with Husky (.husky/pre-commit):
npx docsync check || (echo "README is out of date: run npx docsync sync" && exit 1)10. Architecture
Overview
docsync is a single command line program built as a pipeline. Each stage has one job, and the core stages are pure functions without input or output, which makes them easy to test and makes the output repeatable.
Repository (local folder or GitHub)
|
v
[1] List files ............ skip dependencies, build output, gitignored files, and never .env
|
v
[2] Parse code ............ turn each JS or TS file into a syntax tree (Babel)
|
v
[3] Extract facts ......... env vars, routes, scripts, dependencies, license, folders
| a fact that cannot be determined becomes Not Found
v
[4] Render ................ facts become Markdown tables, always sorted the same way
|
v
[5] Compare or write ...... sync: replace only the marked blocks
check: show a diff, exit 1 when something is out of dateComponents
| Component | Folder | Responsibility |
| --- | --- | --- |
| Command line interface | src/cli/ | Reads the command and options, maps every error to an exit code, prints stack traces only with --debug. |
| Commands | src/commands/ | init, sync and check; decides where the README is read from and written to. |
| Configuration | src/config/ | Merges defaults, docsync.config.json and command line options; validates everything. |
| Sources | src/sources/ | One interface with two implementations: LocalSource (file system) and GitHubSource (GitHub API). Everything after this point does not know where the files came from. |
| Scan scope | src/scope/ | Decides which files are listed, which are analysed, and that .env files are never listed. |
| Code analysis | src/analysis/ | Parses code once and finds environment variable uses and Express routes. |
| Sections | src/sections/ | One module per README section, each with an extract step and a render step. |
| Markers | src/markers/ | Finds the marker blocks and replaces their content without touching the rest of the file. |
| Report | src/report/ | Builds the diff for the terminal and the summary for GitHub Actions. |
| Infrastructure | src/infra/ | Error types, logger with secret masking, safe file writing. |
Key design decisions
- Syntax trees instead of text search. A text search for
.get(also matchesaxios.get('/users')(a call to another API) andmap.get('key'). docsync parses the code and only counts calls on objects it has proven to be an Express app or router. It followsimport,require,exportandmodule.exportsacross files to apply mount prefixes. - Same input, same output. All rows are sorted, paths always use forward slashes, and no dates or line numbers are written. This is what makes
checkreliable: if the code did not change, the README does not change, so there are no false alarms. - Byte exact editing. The marker engine splices new content into the original text by position. It never rewrites the whole file, so line endings (Windows or Unix), a byte order mark and all formatting outside the blocks are preserved. Markers inside code examples are ignored.
- Secrets are excluded by design, not filtered afterwards.
.envfiles are removed when the file list is built, so no later code can read them. From.env.exampleonly the names are read. - One source interface. Local and remote mode share all the logic after the file listing. A test proves both modes produce byte identical output for the same repository.
- Remote mode is consistent and economical. The branch is resolved to one commit first, so all files come from the same commit. Only the files that are needed are downloaded. Before downloading, docsync checks that the remaining API rate limit is enough, and stops early with a clear message if it is not.
- No guessing. A single
Not Foundvalue is used in the code for every unknown fact, and a single function turns it into text. This makes the rule easy to verify.
Data flow of docsync check
user or CI
| docsync check
v
read README and find the marker blocks ..... missing or broken markers: exit 2
|
v
list and read files, run the sections ...... empty repository or API error: exit 3
|
v
compare each block with the fresh content
|
+-- all equal ......... print "README.md is in sync." exit 0
|
+-- differences ....... print a diff per section,
write the GitHub job summary exit 111. Technical details
Technology choices
| Area | Choice | Reason |
| --- | --- | --- |
| Runtime | Node.js 22.12 or newer | Only supported Node.js versions. The projects docsync reads are JavaScript projects, so their developers already have Node.js. |
| Language | TypeScript 6, strict mode, ES modules | Types for the facts model catch mistakes early. |
| Code parsing | Babel parser and traverse 7.29 | One parser for JavaScript, TypeScript and JSX, tolerant of syntax errors. |
| Command line | commander 15 | Standard library for Node.js command line tools; generates the help text. |
| Validation | zod 4 | Validates the configuration file with clear error messages. |
| GitHub API | Octokit REST 22 with throttling and retry plugins | Official client; handles rate limits and temporary errors. |
| Ignore rules | ignore 7 | The same pattern rules as .gitignore. |
| Diffs | diff 9 | Readable unified diffs in the drift report. |
| Tests | Vitest 5 with V8 coverage | Fast and native for TypeScript. |
| Quality | ESLint 10, typescript-eslint, Prettier 3 | Consistent and checked code. |
| Build | tsup 8 | Bundles everything into one file, dist/cli.js. |
Why no AI model: the output has to be correct and repeatable for check to work. A language model can write nice text, but it
can also invent facts and gives different answers each time. docsync only writes facts it can prove from the code.
Why no database: docsync keeps no state between runs. The README itself is the state, and git already stores its history.
Project layout
src/
cli.ts, cli/ command line entry point and option parsing
commands/ init, sync, check
config/ configuration loading and validation
sources/ LocalSource, GitHubSource, GitHub client and error messages
scope/ which files are listed and analysed; .env.example key parser
analysis/ parsing, environment variable analysis, Express route analysis
sections/ the six section modules and their registry
markers/ marker parsing and replacement
core/ facts model, Markdown helpers, pipeline
report/ drift diff and job summary
infra/ errors, logger with secret masking, atomic file writes
tests/
unit/ tests for each module
integration/ end to end acceptance tests through the command line
fixtures/ a sample Express project used by the tests
output/ a README generated from the sample project, kept as evidenceTesting
- 248 tests, about 95 percent of lines covered.
- Acceptance tests run the real command line against a sample Express project and check every acceptance criterion of the user story.
- Secret tests plant fake secret values in
.env, in.env.exampleand in code fallbacks, and check that none of them appears in the README or in any output. - GitHub tests use a fake GitHub API, so the test suite never uses the network.
- Contract test: the same project read locally and through the (fake) GitHub API gives byte identical output.
- Performance test: a project with 500 source files is processed in under 10 seconds (about 1.6 seconds in practice).
- Line ending tests: READMEs with Windows line endings and a byte order mark are preserved exactly.
Adding a new section
Each section is one module in src/sections/ with two functions: extract collects facts from the repository, and render
turns them into Markdown. To add a section, write the module, add its id to src/sections/ids.ts and register it in
src/sections/index.ts. Nothing else needs to change.
12. Security model
| Risk | How docsync handles it |
| --- | --- |
| Secret values in .env files | These files are never listed and never opened, locally or on GitHub. |
| Real values committed to .env.example by mistake | Only the part before = is read; the value is ignored. |
| Secrets in code fallbacks such as process.env.KEY \|\| 'secret' | Literal values from the code are never written to the README. |
| The GitHub token | Read only from the GITHUB_TOKEN environment variable, never from an option, and masked in all output. Only read requests are made. |
| Paths outside the project | File paths are validated, and files outside the project root (also through symbolic links) are never read. |
| Error messages leaking code | Parse errors show only the file, line and column, never the source code. |
| Vulnerable dependencies | npm audit runs in CI and fails on high or critical findings. |
13. Known limitations
- Only JavaScript and TypeScript projects are analysed, and only Express routes are detected.
- Routes registered in loops or helper functions,
process.env[name]with a computed name, and variables read through an alias (const env = process.env) cannot be seen without running the code. - Re-exports of routers are followed one level deep.
- In monorepos only the root
package.jsonis used.package-lock.jsonversion 1 shows the declared version ranges. - Remote mode needs about one API request per source file; very large repositories should be checked out locally.
- Local mode scans the files on disk, including untracked files that are not in
.gitignore. CI checks a clean checkout, so commit or ignore scratch files before runningsync. - npm installs resolve the dependency version ranges at install time; the lockfile only pins this repository's own CI.
14. Roadmap
- A reusable GitHub Action, so other repositories can add the check with a single
uses:line. - Automatic fix pull requests: instead of only failing, CI opens a pull request with the updated README.
- More frameworks: Fastify, Next.js API routes, NestJS and Koa.
- Monorepo support and documentation files other than the README.
15. How this project was built
docsync was built from user story to merged pull request as an agentic software delivery capstone with Claude Code. The AI did the work; a human approved every phase.
User story, Requirements, Architecture, Design review, Plan,
Implementation, Code review, Verification, Pull request| Phase | What happened | Artifact | | --- | --- | --- | | User story | The problem and acceptance criteria | user-story/DOCS-101-docs-sync.md | | Requirements | 12 clarifying questions answered by the human, then 24 functional and 12 non functional requirements | docs/requirements.md | | Architecture | Options compared, 15 components, technology choices | docs/architecture.md | | Design review | 17 findings before any code was written, 16 accepted | docs/design-review.md | | Implementation plan | 18 tasks ordered by dependency | docs/impl-plan.md | | Implementation | Each task tested first and committed on its own | git history | | Code review | 10 findings, 4 real bugs reproduced, fixed and covered by tests | docs/code-review.md | | Independent review | Three reviewer agents with fresh context: 43 findings, 12 code fixes, all Major findings resolved | docs/independent-review.md | | Verification | Full test run, real GitHub API tests, quality check of the generated document | docs/verification.md | | Pull request | Description, changelog and reviewer checklist | docs/pr-description.md |
How Claude Code was used:
| Feature | Where | Purpose |
| --- | --- | --- |
| Project instructions | CLAUDE.md | The pipeline, the approval gates and the rules |
| Skills (slash commands) | .claude/skills/ | One command per phase, from /sdlc-requirements to /sdlc-pr, plus /sdlc-status |
| Agents | .claude/agents/ | Reviewers for the design, the code and the generated document |
| Hooks | .claude/hooks/, .claude/settings.json | Show the pipeline status, block code before the plan is approved, block secrets, run the tests before every commit |
A one page summary for evaluators is in docs/capstone-report.md. The process caught real problems: a wrong rule in the first design, four bugs in the code review, an unclear error found while testing against the real GitHub API, and the hooks themselves not running because of a module setting. Each one was fixed and covered by a test, and each one is recorded in the documents above.
16. Development
To work on docsync itself:
git clone https://github.com/saikusal/docsync.git
cd docsync
npm ci # installs dependencies and builds dist/
npm test # runs all tests
npm run lint # ESLint and Prettier
npm run typecheck # TypeScript
node dist/cli.js check # checks this READMEAfter changing code that affects the facts below, run node dist/cli.js sync and commit the README; the docs-check job
fails otherwise.
17. Project facts (generated by docsync)
Everything below this line is maintained by docsync from this repository and checked in CI.
Overview
| Field | Value |
| --- | --- |
| Name | @saikusal/docsync |
| Description | Keeps a repository's README in sync with its code: env vars, API routes, setup, tech stack and more. |
| Version | 0.1.2 |
| License | MIT |
| Default branch | Not Found |
| Topics | ci, cli, docs, documentation, drift, env, express, github-actions, readme, sync |
| Latest release | Not Found |
Tech stack
- Runtime: Node.js
>=22.12 - Language: TypeScript
| Package | Version | Type |
| --- | --- | --- |
| @babel/parser | 7.29.9 | runtime |
| @babel/traverse | 7.29.8 | runtime |
| @babel/types | 7.29.8 | runtime |
| @octokit/plugin-retry | 8.1.1 | runtime |
| @octokit/plugin-throttling | 11.0.5 | runtime |
| @octokit/rest | 22.0.1 | runtime |
| commander | 15.0.0 | runtime |
| diff | 9.0.0 | runtime |
| ignore | 7.0.11 | runtime |
| zod | 4.6.5 | runtime |
| @types/babel__traverse | 7.28.0 | dev |
| @types/node | 22.20.4 | dev |
| @vitest/coverage-v8 | 5.0.3 | dev |
| eslint | 10.11.0 | dev |
| prettier | 3.9.9 | dev |
| tsup | 8.5.1 | dev |
| typescript | 6.0.3 | dev |
| typescript-eslint | 8.71.0 | dev |
| vitest | 5.0.3 | dev |
Setup
Prerequisites
- Node.js:
>=22.12 - Package manager: npm
Install
npm ciScripts
| Script | Run with | Command |
| --- | --- | --- |
| build | npm run build | tsup |
| docsync | npm run docsync | node dist/cli.js |
| format | npm run format | prettier --write . |
| lint | npm run lint | eslint . && prettier --check . |
| prepare | npm run prepare | npm run build |
| prepublishOnly | npm run prepublishOnly | npm run lint && npm run typecheck && npm test |
| test | npm run test | vitest run |
| test:coverage | npm run test:coverage | vitest run --coverage |
| typecheck | npm run typecheck | tsc --noEmit |
Environment variables
| Variable | Required | Used in |
| --- | --- | --- |
| GITHUB_STEP_SUMMARY | No | src/cli/run.ts |
| GITHUB_TOKEN | No | src/cli/run.ts |
Project structure
| Folder | Purpose |
| --- | --- |
| .claude/ | Claude Code configuration (skills, agents, hooks) |
| .github/ | GitHub workflows and templates |
| docs/ | Documentation |
| src/ | Source code |
| tests/ | Tests |
| user-story/ | Not Found |
License
MIT. Copyright 2026 saikusal.
