repoos
v1.5.0
Published
Reference CLI for RepoOS — validate a repository-local AI knowledge layer and generate vendor instruction files from it.
Maintainers
Readme
repoos — reference CLI
Installs a RepoOS layer into a repository, validates it, generates vendor instruction files from it, and turns agent observations into changes you can review.
Zero dependencies. Node ≥ 16. No lockfile, no transitive packages, no supply-chain surface.
npx [email protected] init . # add the layer — never overwrites anything
npx [email protected] validate . # is the layer well-formed?
npx [email protected] generate . # project it into CLAUDE.md, .cursorrules, …
npx [email protected] propose . # what have agents suggested?The template ships inside this package, so init works from npx with no
checkout, no clone, and no network beyond fetching the package itself.
Pin the version. generate --check compares byte for byte, so an unpinned CLI
is a laptop and a CI runner disagreeing over nothing.
npm i -g repoos if you'd rather have repoos on the path. You can also copy
cli/ into a project and run node cli/bin/repoos.js …, or fetch it from a git
tag — the reference bootstrap
covers offline and registry-free setups.
engines.nodesays>=16, and 16 is the oldest version the code is written for — plain CommonJS, no syntax newer than that. CI proves 18, 20, and 22. The floor is believed good rather than demonstrated, and "believed" is the honest word.
New here? The CLI is one part of a standard. Start at the project README — this file documents the tool, not the thing it operates on.
repoos init [path]
Copies the RepoOS template into a repository. Default target is the current
directory. Full contract: ../tooling/INIT.md.
--dry-run print the plan and change nothing
--suggest print what to add to an existing AGENTS.md; writes nothing
--merge-existing-ai proceed when .ai/ exists but is not RepoOSIt never overwrites a file that already exists. Not AGENTS.md, not .ai/,
not .gitattributes, not a vendor file, not with a flag — there is no
--force. init runs against repositories that already exist and already
work, and the first thing an adoption tool can do to disqualify itself is
destroy the thing it was adopted to improve.
$ repoos init . --dry-run
RepoOS initialization plan
target /home/you/payments-api
template /home/you/payments-api/node_modules/repoos/template
Would create:
+ .ai/repoos.yaml ← project.name: payments-api (from git remote)
+ .ai/workflows/build.md
…
Skipped (left exactly as they are):
· AGENTS.md already exists — RepoOS never rewrites a hand-written entry
point. Run `repoos init --suggest` to see what to add.
Needs your attention:
! CLAUDE.md already exists. `repoos generate` will replace its contents…
No files were changed. Re-run without --dry-run to apply.What it deliberately does not do: create vendor files (generate refuses to
create ones nobody opted into, and routing around that here would be worse),
delete x-repoos-template: true (which would turn your next validate into a
wall of placeholder errors), touch source or CI, run git, or reach the network.
Project discovery reads .git/config as a file, so init works offline.
If .ai/ exists and is not a RepoOS layer, init stops and says so rather than
moving in alongside it. If .ai/repoos.yaml exists, RepoOS is already installed
and init is a reported no-op — there is no re-initialize, because it could only
work by overwriting.
repoos validate [path]
Checks a layer against ../tooling/RULES.md.
--format human|json output format (default: human)
--profile <name> override the declared conformance profileExit codes: 0 no errors · 1 errors found · 2 could not run.
Warnings never affect the exit code. Only errors do. A validator that fails builds over documentation metadata gets removed from CI, and a removed validator means the layer rots silently — which is worse than never having had one.
repoos generate [path]
Projects .ai/ into the vendor files agents actually read — CLAUDE.md,
.cursorrules, .github/copilot-instructions.md, .windsurfrules.
--check compare only; exit 1 if a derived file is stale (CI mode)
--targets a,b which files to emit (default: those already present)This is the tool that makes RepoOS do anything. No AI vendor is obliged to
read .ai/; the generated file is what reaches the agent. A layer that
validates cleanly and is never generated changes no behavior.
It will not create vendor files you haven't opted into. Create an empty
CLAUDE.md and re-run, or configure targets in your manifest:
x-repoos-generate:
targets:
- file: CLAUDE.md
include: [entrypoint, policy, lessons, conventions, workflows]
- file: .cursorrules
include: [entrypoint, conventions, workflows, lessons]
max_bytes: 8000include accepts entrypoint, policy, lessons, conventions, workflows,
and knowledge. Unrecognized names are ignored rather than rejected.
lessons is in every target's default, and emits an index — one line per
unit in .ai/memory/lessons/, with title, tags, summary, and a link — never the
bodies. An approved lesson has to reach whichever assistant a contributor
actually uses, or the value of a promotion depends on the reviewer's choice of
tool. Bodies would grow the always-loaded set without bound, so the generated
text tells the agent to open the linked file when the subject is relevant.
Nothing else under .ai/memory/ is ever derived. inbox/ is low-trust by
construction and archived/ is what stopped being true; both share
type: memory with lessons, so selection is by path. Full contract:
../tooling/GENERATOR.md.
In CI
npx [email protected] validate . && npx [email protected] generate . --check--check is the one blocking check this standard endorses: the failure is
unambiguous, the fix is always the same single command, and it's never a matter
of judgement.
Output is deterministic — the generated date is omitted under --check so
comparison is stable.
repoos propose [path]
Lists what agents have proposed in .ai/memory/inbox/, and promotes one onto a
branch you can review. Full contract: ../tooling/PROPOSE.md.
$ repoos propose .
● The repoos CI job only works while its upstream repo is public
from .ai/memory/inbox/repoos-ci-job-needs-a-public-upstream.md
to .ai/memory/lessons/repoos-ci-job-needs-a-public-upstream.md
conf high
by agent:claude-opus-5/session-2026-08-03
1 proposal, 1 applicable
Apply one: repoos propose --apply=<name>
Reject one: delete the inbox file. Git keeps the history.An agent marks an inbox entry as a proposal by adding x-repoos-proposes::
x-repoos-proposes:
action: promote
target: .ai/memory/lessons/some-lesson.md
reason: >
Why this is worth keeping. Ends up in the commit message.The bare proposes: spelling shipped in 1.3.0 and 1.3.1 and is still read, so
nothing written against those releases breaks. RO-608 warns; it never errors.
The namespaced spelling wins if both appear.
--apply writes the durable unit, deletes the inbox entry, and commits both to
a new branch. --dry-run prints the result without touching git. Neither
pushes — that stays with you:
npx [email protected] propose . --apply=some-lesson
git push -u origin repoos/promote-some-lesson
gh pr create --fillWhat it will not do. Promotion targets are limited to .ai/knowledge/ and
.ai/memory/lessons/, must not already exist, and cannot escape .ai/ — so a
proposal can never reach source code, CI, the agent policy, the manifest, or
decisions/. Escape is blocked as a string and as a path: traversal in either
separator, absolute and UNC paths, embedded NULs, a case-shifted prefix, and a
destination that resolves outside .ai/ through a symlink are all refused. It
also cannot amend an existing unit; corrections are a human edit. The reasoning
for each refusal is in PROPOSE.md, the
machine-readable table is
conformance/proposals.json, and there is
deliberately no flag that relaxes any of it.
If it fails partway, it rolls back. The inbox entry is restored byte for byte, the partial target is removed, and you are returned to the branch you started on. The durable unit can be rebuilt from the observation; the observation cannot be rebuilt from anything.
Rejecting is the normal outcome. Delete the file. §17.3 rule 8 is explicit that most inbox content should be deleted rather than promoted, and a promotion rate near 100% means nobody is really curating.
repoos release check [path]
Proves that every place in a repository stating a version agrees with the one
place that declares it. Aimed at this project's own release process rather than
at adopters, but the mechanism is general. Full contract:
../tooling/RELEASE-CHECK.md.
(no options) check internal consistency; read-only, safe any time
--version=<x.y.z> also check readiness to publish that specific release$ repoos release check .
error versioning-now-cli VERSIONING.md disagrees about the release version
in VERSIONING.md
found 1.4.0
expected 1.4.1
fix Update the 'Now' column in the version table at the top of VERSIONING.md.That is a real finding from this repository, not an illustration: release 1.4.1
moved every other declaration and left that table saying 1.4.0. Nothing failed,
because nothing was comparing them.
The set of locations it checks is data, in release.json, not code. It is
not a grep for the current version — changelog history, migration guides, and
version examples inside the specification are explicitly out of scope, because a
checker that demanded history be rewritten would be switched off within a week.
A declaration that can no longer be located is an error rather than a skip, so
restructuring a document cannot silently disable the check that reads it.
Unlike validate, errors here are meant to block a build: a version
contradiction is unambiguous and mechanically fixable, the same bar
generate --check clears. It never tags, pushes, or publishes, and there is no
subcommand that does.
The = is required. Bare --version prints this tool's version, and a form that
consumed the next token would make release check --version 1.4.2 print 1.4.1
and exit 0.
The YAML subset — read this before trusting it
The parser is hand-written and covers only what RepoOS files use. It is verified against all 60 YAML documents and frontmatter blocks in this repository.
Supported: block mappings and sequences · nested structures · inline flow
({ a: 1 }, [a, b]) · single and double quoted scalars · block scalars
(>, |, >-, |-) · comments, including # inside quoted strings ·
booleans, integers, floats, null/~ · sequences of mappings.
Explicitly rejected — these throw with a line number rather than being
misparsed: anchors and aliases (&/*) · type tags (!!) · complex keys
(?) · tab indentation · multi-document streams.
The design rule is never guess. A parser that silently returns the wrong object turns a real violation into a false pass, and a validator that incorrectly reports "clean" is worse than no validator. If your YAML is outside the subset you get a clear error, not a wrong answer.
If you need full YAML, reimplement against
../tooling/VALIDATOR.md with a real parser and
check yourself against ../conformance/.
Rules not implemented
This CLI implements every rule needed for conformance plus the tractable
warnings. It deliberately skips the rest and says so — --format json returns
them in a not_implemented array.
| Rule | Why not |
|---|---|
| RO-105, RO-204 | JSON Schema validation. Out of scope for zero-dep; use ../schemas/ with a JSON Schema library. The hand-coded RO-1xx/RO-2xx field checks cover the same ground. |
| RO-305, RO-306, RO-503, RO-504 | Heuristics. A validator cannot determine what an arbitrary command does, and a noisy check gets ignored. |
| RO-601 | Staleness. Needs --changed-files plus VCS history. |
| RO-603 | Memory/knowledge contradiction. Needs semantic comparison. |
| RO-606 | Derived-file drift — use generate --check instead. |
A validator that silently omits half its registry looks complete and isn't.
Tests
npm run selfcheck # every canonical check, in the order CI runs them
npm test # alias for check:test — the fast in-memory suites
npm run check:package # npm pack → install → init, from the real tarballselfcheck runs seven check:* scripts — check:test, check:repoos,
check:template, check:json, check:generated, check:release,
check:package — and .github/workflows/ci.yml runs the same seven as named
steps. Each is defined once, in package.json, so there is no list to keep in
sync; test/checks.js asserts it.
| Suite | Asserts | Source of truth |
|---|---|---|
| test/checks.js | That selfcheck and CI run the same checks in the same order, 14 assertions | package.json + ../.github/workflows/ci.yml |
| test/conformance.js | Exit codes and error-severity rule IDs, 12 fixtures | ../conformance/expected.json |
| test/propose.js | The promotion containment table, 83 checks | ../conformance/proposals.json |
| test/generate.js | What may and may not reach a derived file, 75 checks | ../conformance/proposals.json |
| test/init.js | That init creates what is missing and never overwrites what is not, 119 checks | ../tooling/INIT.md |
| test/release.js | That version disagreements are caught and history is not, 87 checks | ../release.json |
| test/package.js | That the published tarball can run init offline, 56 checks | package.json files |
None of them touch the network, the clock, or git, so there are no flaky cases.
The first four render everything in memory; init and release write to a
temp directory and remove it. test/package.js additionally shells out to npm,
which is why it is check:package rather than part of check:test — it is also
the only test that can tell "works in this checkout" from "works when
published", since every other one resolves the template as ../template.
test/checks.js runs first, and is the odd one out: its subject is the build
rather than the code. It reads package.json and ../.github/workflows/ci.yml
and fails if the canonical checks, selfcheck, and CI have stopped agreeing —
which they had, in both directions, before it existed.
The tables are not written in the test files. They are read from
../conformance/, so the assertions this implementation is held to are the ones
an independent implementation can read without opening any of this source. A
containment rule that exists only inside the reference CLI is not part of the
standard, however carefully it is enforced.
Building your own implementation? Check it against the same files. If an assertion looks wrong, that's worth reporting — ambiguity in the contract is what produces tools that disagree.
MIT. This is a reference implementation, not the standard. The standard is
../SPECIFICATION.md, and it is language-agnostic by
design.
