mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* fix(hooks): guard on the command the hook actually runs
pre-commit probed one binary and invoked another. The protocol block
guarded on `command -v go` and then ran `make protocol-verify`; the sqlc
block guarded on `command -v sqlc` and ran `make sqlc-verify`. `make` is
not on PATH on a stock Windows box, so a contributor with Go installed
but no make had their commit rejected with
pre-commit: FAIL: protocol constants are stale — run 'make
protocol-generate' in Server/ and stage the result
when nothing had been generated and nothing compared. The real cause was
`make: not found`, and the advice the message gives fails the same way.
Rather than add a `command -v make` guard, inline what the two Makefile
targets reduce to — `sqlc generate` / `go run ./scripts/genprotocol`
followed by `git diff --exit-code`. Same semantics, one less prerequisite,
and it doubles as the make-free equivalent B1-2 asks for. The Makefile
targets stay for anyone who prefers them.
Also: the protocol block was the only one with no `else`, so a
contributor without Go got no check and no notice. It now warns like its
two siblings. And gofmt is a separate binary from go, so it is probed
separately.
Verified both directions with the hook body replayed verbatim:
- Go present, make absent -> passes, no staleness claimed.
- schema edited without regenerating -> fails, as it must.
Refs RL-20 / L-14.
* docs: state one branch and PR model
Active documents contradicted each other head-on. README.md and
docs/contributing.md said branch from `dev` and target `dev`; CLAUDE.md
said branch from `main`, PR to `main`. That is R-02, and the 2026-08-19
audit had already recorded it as D-06 without it being resolved.
`dev` is the answer, and the repository already behaves that way: B0 made
`dev` PR-only with ten required checks enforced on admins, and #1409,
#1410 and #1411 all landed there. `main` carries releases.
docs/contributing.md becomes the single source of truth. It now states the
model, what protection is actually applied, and the two consequences a
contributor meets on their first PR — that a self-mergeable PR still cannot
merge red, and that Docker and Tauri Full Build report as skipped against
`dev` rather than failing. Everywhere else summarises and links here.
- CLAUDE.md: corrected, with a link rather than a second copy.
- CONTRIBUTING.md: new. GitHub's contributing-guidelines affordance only
resolves the root, .github/ or docs/ — `docs/contributing.md` is not a
path it finds, so the link never appeared on issues or PRs.
- PULL_REQUEST_TEMPLATE.md: names the base branch, which it did not.
- bughunt-run skill: reviewed the branch against `origin/main`, which is
the wrong base once every PR targets `dev`.
README.md already said `dev` and is left as the short summary it should be.
Dated audits and the historical remediation plan keep their `main`-era
wording — they are records.
Refs R-02.
* fix(hooks): pick the pre-push base from the nearest integration branch
pre-push decided which side's gates to run from
`git diff --name-only origin/main...HEAD`. That was right when everything
targeted `main`. Once `dev` became the integration branch it stopped being
right: a branch cut from `dev` diffed against `main` counts everything on
`dev` and not yet on `main` as "changed".
Measured on this branch: the old base reported 609 changed files, the new
one reports 6. So in practice the hook was running the full server build
matrix and the client typecheck plus eslint on every push, whatever the
change touched — the file-based narrowing it exists for never engaged.
Now it picks whichever of origin/dev, origin/main is nearest, by commits
between merge-base and HEAD, skipping a candidate that scores 0. Verified:
a branch cut from dev picks origin/dev (2 ahead); dev itself scores 0
against dev and picks origin/main (8 ahead), which is what a dev -> main
release PR wants. With no candidate resolvable it falls back to the
existing `__all__`, so an unfetched or shallow clone still runs everything.
Refs RL-20 / L-14, R-02.
* chore(node): one Node source of truth
`.nvmrc` and all ten `actions/setup-node` pins said 24; five active
documents and the repo's only `engines` block still said 20. A contributor
following the docs installed a version CI does not run.
Node 24 wins — it is what CI already runs. Every manifest now declares
`engines`, and `engine-strict=true` turns a wrong major into a failed
install rather than an `EBADENGINE` warning nobody reads. `>=24` rather
than `^24` so a Node 26 box keeps working; `Client/.nvmrc` stays the
human-facing pin and the docs point at it instead of restating a number.
The `.npmrc` is per package root, not one at the top. npm reads the
project `.npmrc` from the package directory and does not walk parents —
verified with a throwaway package requiring node >=99: with only a parent
`.npmrc` npm warned and exited 0; with one in the package directory it
failed `notsup`. A single root file would have left `Client/`, the package
that matters most, on warnings.
Five docs, not the four previously identified — `docs/mcp-introspect.md`
also said 20. And `docs/contributing.md` claimed "`.nvmrc` + CI both say
Node 20", which was wrong about both.
Verified both directions in all three package roots: Node 22 fails
`notsup`; Node 24 installs clean and `npm ci` passes in Client/.
Refs RL-17 / C-01, ENV-01.
* docs: add the documentation landing page
`docs/` had 24 top-level files and no index. The root README carried a
flat list of 22 links that had drifted: six documents were reachable from
nowhere at all — including both 2026-08-23 audits and the test audit — and
two entries were labelled "latest" while newer unlinked audits existed.
docs/README.md is the index RL-12 asked for. It groups by what a document
*is*, because that is what decides whether to trust it: guidance tells you
how to do something, reference describes a contract the code implements,
audits are dated snapshots nobody updates, plans record intent. Every
tracked file under docs/ now appears exactly once, and the audit table says
plainly that audit-2026-08-19.md still claims "0 open findings" when the
ledger has 38.
The root README keeps a short curated list and defers to the index, rather
than maintaining a second copy that drifts again. Two fixes while there:
`docs/plans/` was linked as a bare directory, unlike its two sibling
directory entries, and was annotated "each carries a verified status
header" — which docs/plans/README.md:7-9 explicitly contradicts, since a
plan's header is exactly the thing that drifts and the index is the
authority.
Verified: 78 relative links across the new and edited files resolve, and
no tracked docs/ file is unreachable from the index.
Refs RL-12 / R-06.
* feat(scripts): root command facade
Entry points existed only inside Server/ (a Makefile) and Client/ (npm
scripts). Nothing at the root told a new contributor where to start, and
the root package.json had three scripts, none of which built or tested
anything.
`npm run check` from the root now runs what CI gates on, and
check:server / check:client / check:rust run one stack. scripts/run.mjs
is dependency-free Node — the shape render-ledger.mjs already uses — so
`npm run check` works before `npm install` has.
Cross-platform by construction: every step is spawned with an explicit cwd
and no shell, so there is nothing to quote and no `cd &&` to behave
differently on Windows. npm and npx get their .cmd suffix there. No step
shells out to make.
The facade orchestrates; it is not a new required path. Each step prints
the command and the directory before running it, and those are exactly the
commands documented per-stack — so a server contributor can read the output
and type them instead, and still never needs Node. Tools CI installs but a
contributor may not have (golangci-lint, which has no wrapper in this repo
at all; sqlc, pinned by Server/sqlc.version) are skipped with a printed
reason rather than failing.
Three corrections to the ci-check skill while aligning it:
- `make sqlc-verify protocol-verify` replaced by what those targets reduce
to, so the documented path does not require make either.
- `cargo test` -> `cargo test --lib`, which is what ci.yml actually runs.
- "NODE_OPTIONS=--no-experimental-webstorage is mandatory on Node 22+" was
false. tests/setup.ts installs the shim, CI runs Node 24 without the
flag, and the suite was measured passing without it — 192 files / 5257
tests, identical to the flagged run.
Also documents the third RL-20 problem, which needed no code: core.hooksPath
is exclusive, so `npm run hooks:install` silently disables any
.git/hooks/post-commit — including the one `graphify hook install` writes,
which CLAUDE.md tells agents to install. Nothing warned about that.
Verified: check:client 5257/192 green, check:rust 123 tests + clippy green,
--list prints every command, and the optional-tool skip path reports rather
than fails.
Refs RL-04 / L-04, RL-20 / L-14.
* feat(ci): fail on a document that contradicts the findings ledger
G-04's remaining half. The ledger is the source of truth for defect counts,
but nothing stopped a planning document from stating a different number and
nothing noticed when one did. `render-ledger.mjs --check` cannot help: it
validates the JSON schema and returns before rendering, so it never reads
FINDINGS.md and cannot see drift at all — and no workflow ran it anyway.
scripts/check-doc-counts.mjs counts ledger statuses and compares them to
what an allow-list of active documents claims, failing with file, line,
claimed value and actual. Wired into ci.yml as a job with no npm ci, since
the script imports nothing outside node:, and into the facade as
`npm run check:docs` — first in `check`, so a contradicted count does not
wait behind ten minutes of -race.
The patterns are narrow on purpose. A first attempt matched any
"<number> <status>" and flagged nineteen things, all false: "the 45 open P1
rows" (issue-register rows, not ledger findings), "All 8 findings F1-F8"
(a different register), "G-05 **refuted**" (an identifier), `">=20"` and
`CGO_ENABLED=0` (not counts at all). A check that cries wolf gets ignored,
which is the failure G-04 already describes. So a number is only read as a
claim in three shapes that cannot mean anything else: an enumeration of two
or more "<n> <status>" pairs, a status table row in a table that totals
itself, and "<n> records/findings" where the ledger is named within three
lines. Fifteen selftest assertions pin both directions, and the job runs
them before it runs the check.
It reads findings-ledger.json directly rather than importing
render-ledger.mjs for `validate`/`render`: that module ends in a bare
top-level `await main()` with no import.meta.main guard, so importing it
rewrites FINDINGS.md as a side effect.
Dated docs/audit-*.md are reported, never failed — they are snapshots
nobody maintains. audit-2026-08-19.md does claim zero open findings against
38 open, so b0-baseline's "No plan was found claiming '0 open findings'"
holds for docs/plans/ but not for docs/.
Not included: a real FINDINGS.md render-drift check. That is RL-07 and
belongs with the generated-artifact work, not here.
Verified: 27 claims across 9 documents agree; corrupting one count in
docs/plans/README.md fails the check naming that line, for both the status
and the total.
Refs G-04.
---------
Co-authored-by: Claude <noreply@anthropic.com>
174 lines
6.4 KiB
JavaScript
174 lines
6.4 KiB
JavaScript
#!/usr/bin/env node
|
|
// Root command facade (RL-04 / L-04).
|
|
//
|
|
// One entry point for the checks CI runs, so a contributor does not have to
|
|
// know which directory each stack lives in. `node scripts/run.mjs --list`
|
|
// prints every task and the exact commands it runs.
|
|
//
|
|
// Two rules this file exists to keep:
|
|
//
|
|
// 1. Cross-platform. No `make`, no shell syntax, no `cd &&`. Every step is
|
|
// spawned directly with an explicit `cwd`, so there is no shell to quote
|
|
// for and nothing that behaves differently on Windows.
|
|
// 2. The facade orchestrates, it never becomes the only path. Each step
|
|
// prints the command it runs, in the directory it runs it in, so a
|
|
// Go-only contributor can read the output and type those commands
|
|
// instead — and never needs Node to work on the server.
|
|
//
|
|
// Dependency-free by design: Node's standard library only, like
|
|
// .superpowers/render-ledger.mjs. Adding a dependency here would mean
|
|
// `npm run check` could not run until `npm install` had.
|
|
|
|
import { spawnSync } from 'node:child_process'
|
|
import { existsSync } from 'node:fs'
|
|
import { dirname, join, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const WIN = process.platform === 'win32'
|
|
|
|
// npm and npx are batch shims on Windows; everything else is a real binary.
|
|
const bin = (c) => (WIN && (c === 'npm' || c === 'npx') ? `${c}.cmd` : c)
|
|
|
|
/** A step that always runs. */
|
|
const step = (cmd, args, cwd = '.') => ({ cmd, args, cwd })
|
|
|
|
/**
|
|
* A step that is skipped, with a printed reason, when `probe` is not on PATH.
|
|
* Used for tools CI installs but a contributor may not have: golangci-lint has
|
|
* no wrapper in this repo at all, and sqlc is pinned by Server/sqlc.version.
|
|
*/
|
|
const optional = (probe, cmd, args, cwd, why) => ({ cmd, args, cwd, probe, why })
|
|
|
|
// `git diff --exit-code` after regenerating is what `make protocol-verify` and
|
|
// `make sqlc-verify` reduce to. Inlined so neither needs make.
|
|
const PROTOCOL_VERIFY = [
|
|
step('go', ['run', './scripts/genprotocol'], 'Server'),
|
|
step('git', ['diff', '--exit-code', 'ws/message_types.go', '../Client/src/lib/protocolTypes.ts'], 'Server'),
|
|
]
|
|
const SQLC_VERIFY = [
|
|
optional('sqlc', 'sqlc', ['generate'], 'Server', 'sqlc not on PATH — install the version in Server/sqlc.version'),
|
|
optional('sqlc', 'git', ['diff', '--exit-code', 'db/dbgen'], 'Server', 'sqlc not on PATH'),
|
|
]
|
|
|
|
const CHECK_SERVER = [
|
|
step('go', ['build', './...'], 'Server'),
|
|
step('go', ['build', '-tags', 'otel', './...'], 'Server'),
|
|
step('go', ['build', '-tags', 'wazero', './...'], 'Server'),
|
|
step('go', ['build', '-tags', 'otel,wazero', './...'], 'Server'),
|
|
step('go', ['vet', './...'], 'Server'),
|
|
step('go', ['test', '-race', './...'], 'Server'),
|
|
step('go', ['test', '-tags', 'deadlock', '-count=1', './ws/'], 'Server'),
|
|
optional('golangci-lint', 'golangci-lint', ['run', './...'], 'Server', 'golangci-lint not on PATH — CI pins v2.11.3'),
|
|
...PROTOCOL_VERIFY,
|
|
...SQLC_VERIFY,
|
|
]
|
|
|
|
const CHECK_CLIENT = [
|
|
step('npm', ['run', 'typecheck'], 'Client'),
|
|
step('npm', ['run', 'lint'], 'Client'),
|
|
step('npm', ['run', 'format:check'], 'Client'),
|
|
step('npm', ['test'], 'Client'),
|
|
]
|
|
|
|
// Matches ci.yml's Rust Unit Tests job exactly: --lib for tests, --all-targets
|
|
// for clippy. They differ deliberately; do not "align" them.
|
|
const CHECK_RUST = [
|
|
step('cargo', ['test', '--lib'], 'Client/src-tauri'),
|
|
step('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'], 'Client/src-tauri'),
|
|
]
|
|
|
|
// Fast and dependency-free, so it goes first: a contradicted count should not
|
|
// wait behind ten minutes of -race.
|
|
const CHECK_DOCS = [step('node', ['scripts/check-doc-counts.mjs'], '.')]
|
|
|
|
const TASKS = {
|
|
bootstrap: [
|
|
step('npm', ['ci'], '.'),
|
|
step('npm', ['ci'], 'Client'),
|
|
step('npm', ['ci'], 'tools/mcp-introspect'),
|
|
],
|
|
'check:server': CHECK_SERVER,
|
|
'check:client': CHECK_CLIENT,
|
|
'check:rust': CHECK_RUST,
|
|
'check:docs': CHECK_DOCS,
|
|
check: [...CHECK_DOCS, ...CHECK_SERVER, ...CHECK_CLIENT, ...CHECK_RUST],
|
|
generate: [
|
|
step('go', ['run', './scripts/genprotocol'], 'Server'),
|
|
optional('sqlc', 'sqlc', ['generate'], 'Server', 'sqlc not on PATH — install the version in Server/sqlc.version'),
|
|
],
|
|
format: [
|
|
step('npm', ['run', 'format'], 'Client'),
|
|
optional('gofmt', 'gofmt', ['-w', '.'], 'Server', 'gofmt not on PATH'),
|
|
],
|
|
'release:preflight': [
|
|
...CHECK_DOCS,
|
|
...CHECK_SERVER,
|
|
...CHECK_CLIENT,
|
|
...CHECK_RUST,
|
|
step('npm', ['run', 'build'], 'Client'),
|
|
],
|
|
}
|
|
|
|
function onPath(cmd) {
|
|
const probe = spawnSync(WIN ? 'where' : 'command', WIN ? [cmd] : ['-v', cmd], {
|
|
stdio: 'ignore',
|
|
shell: !WIN, // `command` is a shell builtin; `where` is a real binary
|
|
})
|
|
return probe.status === 0
|
|
}
|
|
|
|
function runTask(name) {
|
|
const steps = TASKS[name]
|
|
if (!steps) {
|
|
console.error(`unknown task: ${name}\nknown: ${Object.keys(TASKS).join(', ')}`)
|
|
process.exit(2)
|
|
}
|
|
const skipped = []
|
|
for (const s of steps) {
|
|
if (s.probe && !onPath(s.probe)) {
|
|
console.log(`\n--- SKIP ${s.cmd} ${s.args.join(' ')} (${s.why})`)
|
|
skipped.push(s.probe)
|
|
continue
|
|
}
|
|
const where = s.cwd === '.' ? '' : ` [in ${s.cwd}]`
|
|
console.log(`\n--- ${s.cmd} ${s.args.join(' ')}${where}`)
|
|
const r = spawnSync(bin(s.cmd), s.args, {
|
|
cwd: join(ROOT, s.cwd),
|
|
stdio: 'inherit',
|
|
shell: false,
|
|
})
|
|
if (r.error && r.error.code === 'ENOENT') {
|
|
console.error(`\nFAILED: ${s.cmd} is not installed or not on PATH.`)
|
|
process.exit(1)
|
|
}
|
|
if (r.status !== 0) {
|
|
console.error(`\nFAILED: ${s.cmd} ${s.args.join(' ')}${where} exited ${r.status}`)
|
|
process.exit(r.status ?? 1)
|
|
}
|
|
}
|
|
if (skipped.length) {
|
|
console.log(`\n${name}: passed, with ${[...new Set(skipped)].join(', ')} skipped (not installed). CI runs them.`)
|
|
} else {
|
|
console.log(`\n${name}: passed`)
|
|
}
|
|
}
|
|
|
|
const arg = process.argv[2]
|
|
if (!arg || arg === '--list') {
|
|
for (const [name, steps] of Object.entries(TASKS)) {
|
|
console.log(`\n${name}`)
|
|
for (const s of steps) {
|
|
const where = s.cwd === '.' ? '' : ` (in ${s.cwd})`
|
|
console.log(` ${s.probe ? '[optional] ' : ''}${s.cmd} ${s.args.join(' ')}${where}`)
|
|
}
|
|
}
|
|
console.log('')
|
|
process.exit(0)
|
|
}
|
|
if (!existsSync(join(ROOT, 'Server')) || !existsSync(join(ROOT, 'Client'))) {
|
|
console.error('run this from the repository root')
|
|
process.exit(2)
|
|
}
|
|
runTask(arg)
|