Files
Benjamin Diedrichsen 7862fab809 [docs] keyman: rewrite the README, close the audit
Phase 9 of packages/keyman/docs/PLAN.md; closes AUDIT §5.2, §5.3, §5.4, §5.5,
and the §4 one-liners no phase had claimed (§4.1–§4.4, §3.7).

The README is the only document that ships (package.json files: dist,
README.md, LICENSE), and it described four of nine menu entries, invented key
rotation, told the user to run ssh-keygen by hand, asked them to write a
.gitignore keyman now writes, and mentioned none of the command line. It is
rewritten against the code: every operation, the rotate/retire sequence, the
id_ prefix and what happens to keys without it, installation with the scope
mapping (never a bare --registry, which would send 55 transitive dependencies
to a registry that has never heard of them), the configuration semantics
including which relative path resolves against what, and the Phase 5 migration
for a split vault.

The CLI section is helpText() verbatim, with tests/readme.test.ts asserting the
two are identical and that every menu label appears — so a flag or an operation
added later fails the gate instead of shipping undocumented. That is the part
that keeps this from drifting again.

Also: index.ts loses the bin's shebang (it is only ever imported), exports the
config types so a consumer can name what loadConfig returns, and re-exports the
update module wholesale rather than half of it by name — verified by importing
the built dist/index.js and reading its keys. The narrow surface is now a
comment stating the rule rather than an accident.

AUDIT.md marks all 30 findings closed except the second half of §1.8, keeping
each finding's text as the record with what closed it quoted underneath, the
way DOCS-AUDIT.md does. PLAN.md gains a status section naming the three
deviations. Root CLAUDE.md records the keyman architecture as it now is,
including the deliberate `resolution` divergence from nopy.

DOCS-AUDIT.md §2.10, §6.4 and the §7 keyman-config entry are amended in the
working tree but left unstaged, since that file carries unrelated WIP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 17:57:00 +02:00

48 lines
1.8 KiB
TypeScript

/**
* The README is the only document that ships (`package.json` files: dist,
* README.md, LICENSE), so a reader on the registry sees it and nothing else. It
* had drifted to describing four of the menu's entries and none of the command
* line; these assertions are the parts that can drift again silently.
*/
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { helpText } from '../src/keyman.args.js';
const README = fs.readFileSync(path.join(import.meta.dirname, '..', 'README.md'), 'utf-8');
describe('README', () => {
it('quotes --help verbatim', () => {
// Copied rather than described, and checked rather than trusted: a flag added
// to helpText() now fails here instead of shipping undocumented.
expect(README).toContain(helpText().trim());
});
it('documents every menu operation', () => {
const main = fs.readFileSync(
path.join(import.meta.dirname, '..', 'src', 'keyman.main.ts'),
'utf-8'
);
const labels = [...main.matchAll(/\{ name: '([^']+)', value: '[a-z]+' \}/g)].map((m) => m[1]);
// The labels themselves, emoji included, so a renamed entry is caught too —
// but with runs of whitespace collapsed, because some of them carry a second
// space to align a variation-selector emoji in a terminal, and prose should
// not have to reproduce that.
const collapse = (text: string) => text.replace(/\s+/g, ' ');
const readme = collapse(README);
expect(labels.length).toBe(9);
for (const label of labels) {
expect(readme, label).toContain(collapse(label));
}
});
it('documents every configuration key', () => {
for (const key of ['vaultRoot', 'keysDir', 'tmpDir', 'ageKeyFile']) {
expect(README, key).toContain(key);
}
});
});