A cold agent was given this repo and the documents it links to, and nothing else — no core source, no module-uo — and asked to build a module for a second game. It did, in one pass. The record is docs/modules/kit-acceptance.md; this is the repair list, plus the two things it recommended that were not defects. The one it could not find, because it had no core to render against: a module page built exactly as this kit teaches renders OUTSIDE the site. PublicLayout is the chrome, not the body. Core grew an opt-in `shell` prop for it (MODULE_API_VERSION 1.5.0, website#148); the template passes shell="narrow" and chapter 2 explains why you name a width and never a class. Fixed: - **F1, and the worst of them, because it lands in the first twenty minutes.** `npm run check:swagger` failed on a PRISTINE template on Windows: the check compared the committed fragment byte-for-byte and a default Windows clone is CRLF while the generator writes LF. The message blamed "the routes or their annotations". Now `template/.gitattributes` pins `eol=lf` and the comparison normalises line endings anyway — a check may only fail for the reason it names, and this one names a diagnosis. - **F3** — `.gitea/workflows/release.yml` carries `gitea.example.com` and `your-org/your-module` under a literal `# CHANGE THESE`, was not in the rename checklist, and `checkRenameSites.js` could not match it, so CI was silent by construction. Row added, pattern widened. (The agent reported both workflow flavours; only the Gitea one is affected — GitHub supplies its own variables. Corrected in the record.) The near-miss is kept in the check's comments and its suite: the obvious widening is `example\.com`, which fires on a fixture URL in checkImports.test.js. Every alternative has to be a string that cannot occur by accident, which is the same rule that made the id `examplegame`. - **F4** — the release bundle's include list was hardcoded, so adding `server/utils/` would have silently dropped it from every release while the bundle check stayed green. Inverted to an exclusion list, in both flavours, and run by hand because a release workflow never executes in CI. - **F5** — the annotation-quoting warning was wrong in both directions, and the correction is measured rather than reasoned. A backtick is harmless (the template's own description has two spans and they survive). A `"` is not, and it does not throw: `'A "quoted" status'` is silently TRUNCATED to `A "` while swagger-autogen prints Success and the error capture sees nothing. The only signal is check:swagger blaming your routes. - **F6** — `template/.gitignore`, so a copied template that is `git init`ed inherits ignore rules instead of nothing. - **F7** — the UI kit is eight exports across five rows, not seven. The contract said seven and this kit had faithfully carried the miscount out of it. Adopted, not defects: - Chapter 1 now says to run every check on the untouched copy first. That is what found F1; without a baseline the first failure is ambiguous forever. - The template ships the §2.7 self-check the agent wrote for itself. The rule has no CI in general — an outbound socket is not statically detectable — but a module can make a decidable claim about its own tree. Ported from its code with a header explaining how to NARROW it when a sidecar client arrives, since talking to your sidecar is the expected shape and is not what §2.7 forbids. The pin moves to website edge 4ad8b2b, the 1.5.0 bump, and template/module.json declares ^1.5.0 — so checkCoreApi's equality assertion still holds and the template uses a member that exists only at that ref and later. 32 server + 18 client template tests, 21 kit-script tests, all four checks green. Co-Authored-By: Claude <noreply@anthropic.com>
120 lines
4.7 KiB
JavaScript
120 lines
4.7 KiB
JavaScript
// The rename check, checked.
|
|
//
|
|
// A check that has never been shown to fail is a check nobody knows the state of.
|
|
// This one gates the only instructions a reader has for the first thing they do
|
|
// with the template, so both directions of it are exercised here against
|
|
// fixtures — no filesystem, because `problems()` takes its three inputs as
|
|
// arguments precisely so that it can be tested this way.
|
|
//
|
|
// Run by CI as `node --test scripts/`, which needs no dependencies and no
|
|
// package.json: Node's own test runner, over a repo with nothing installed.
|
|
|
|
const test = require('node:test')
|
|
const assert = require('node:assert')
|
|
|
|
const { PLACEHOLDER, checklistPaths, problems } = require('./checkRenameSites')
|
|
|
|
/** `problems()` with a `contains` built from a set of file names. */
|
|
const check = (files, listed, dirty) =>
|
|
problems({ files, listed, contains: (f) => new Set(dirty).has(f) })
|
|
|
|
test('a clean, complete checklist has no problems', () => {
|
|
assert.deepStrictEqual(check(['a.js', 'b.js', 'clean.js'], ['a.js', 'b.js'], ['a.js', 'b.js']), [])
|
|
})
|
|
|
|
test('a file that mentions the placeholder and is not listed fails', () => {
|
|
const found = check(['a.js', 'new.js'], ['a.js'], ['a.js', 'new.js'])
|
|
assert.strictEqual(found.length, 1)
|
|
assert.match(found[0], /new\.js.*NOT in the rename checklist/s)
|
|
})
|
|
|
|
test('a listed file that no longer mentions the placeholder fails', () => {
|
|
// The direction that is easy to leave out, and the more valuable of the two: a
|
|
// row that has stopped matching reads as instructions to edit something that is
|
|
// not there any more.
|
|
const found = check(['a.js', 'b.js'], ['a.js', 'b.js'], ['a.js'])
|
|
assert.strictEqual(found.length, 1)
|
|
assert.match(found[0], /b\.js.*no longer mentions/s)
|
|
})
|
|
|
|
test('a listed file that has been deleted fails', () => {
|
|
const found = check(['a.js'], ['a.js', 'gone.js'], ['a.js'])
|
|
assert.strictEqual(found.length, 1)
|
|
assert.match(found[0], /gone\.js.*does not exist/s)
|
|
})
|
|
|
|
test('a duplicated row fails', () => {
|
|
const found = check(['a.js'], ['a.js', 'a.js'], ['a.js'])
|
|
assert.ok(found.some((p) => /listed in the checklist twice/.test(p)))
|
|
})
|
|
|
|
test('the checklist file itself is exempt', () => {
|
|
// It is a table OF the placeholder, so it would otherwise always list itself.
|
|
assert.deepStrictEqual(check(['README.md'], [], ['README.md']), [])
|
|
})
|
|
|
|
test('the placeholder pattern matches every form a rename touches', () => {
|
|
for (const text of [
|
|
"const ID = 'examplegame'",
|
|
'ExamplegameWorldStatus',
|
|
'name: "Example Game"',
|
|
"worldName: 'Example World'",
|
|
'examplegame_world_status',
|
|
'example-game',
|
|
// The publishing pair, added after the acceptance run found the release
|
|
// workflow unlisted and unmatchable (kit-acceptance.md F3).
|
|
' GITEA_HOST: gitea.example.com',
|
|
' REPO: your-org/your-module',
|
|
]) {
|
|
assert.ok(PLACEHOLDER.test(text), `should match: ${text}`)
|
|
}
|
|
})
|
|
|
|
test('the placeholder pattern does not fire on ordinary prose', () => {
|
|
// The reason the id is `examplegame` rather than `example`: a check that
|
|
// false-alarms on the word "example" in a comment is a check whose failures
|
|
// stop being read.
|
|
for (const text of [
|
|
'// for example, a router mounted under /shard',
|
|
'an example of what to catch',
|
|
'exampleValue',
|
|
'the game world',
|
|
// A real span from server/test/checkImports.test.js, and the reason the
|
|
// publishing host is matched in full rather than as `example.com`: it is a
|
|
// fixture URL inside a string, in a test about URLs inside strings, and it is
|
|
// not a rename site. The obvious widening would have failed the build on it.
|
|
"const url = 'https://example.com/x'",
|
|
// Prose about the reader's own module, which is not the hyphenated token.
|
|
'copy your module directory onto the volume',
|
|
'your org will need a release token',
|
|
]) {
|
|
assert.ok(!PLACEHOLDER.test(text), `should not match: ${text}`)
|
|
}
|
|
})
|
|
|
|
test('checklistPaths reads only the rows between the markers', () => {
|
|
const md = [
|
|
'# Heading',
|
|
'',
|
|
'Prose quoting `not/a/row.js` and a tree diagram.',
|
|
'',
|
|
'<!-- rename-sites -->',
|
|
'',
|
|
'| File | What to change |',
|
|
'| --- | --- |',
|
|
'| `module.json` | the id |',
|
|
'| `server/core.js` | the message |',
|
|
'',
|
|
'<!-- /rename-sites -->',
|
|
'',
|
|
'More prose about `also/not/a/row.js`.',
|
|
].join('\n')
|
|
assert.deepStrictEqual(checklistPaths(md), ['module.json', 'server/core.js'])
|
|
})
|
|
|
|
test('a README with no markers is an error, not an empty checklist', () => {
|
|
// Silently reading zero entries would make every later assertion vacuous, and
|
|
// the check would pass on a README whose checklist someone deleted.
|
|
assert.throws(() => checklistPaths('# Heading\n\nno markers here\n'), /rename-sites/)
|
|
})
|