Files
software-mansion__argent/scripts/check-workspace-versions.mjs
filip131311 b232114422 docs: audit every comment in src against the code it describes (#931)
Audits every comment in `src/` and `scripts/` against the code it
describes.

**462 files, 57 commits, net −8,194 lines.** Comment text only — the
whole branch is code-identical to `main`.

## Method

One subagent per file, strictly sequential. Scope was `src/` +
`scripts/` (459 files); three more files were added at the end because
they carried dead references of the same kind — two test headers citing
design docs that do not exist, and `publish-npm.yml` citing a retired
workflow. Each agent verified every comment — line, block, JSDoc, file
header, trailing — against the surrounding code and the rest of the
repo, following identifiers, paths, tool ids, config keys, env vars and
issue links to see whether they still exist and still behave as
described. Rules:

- **A false or misleading comment is deleted, not reworded.** If a claim
could not be confirmed by reading the source, it went. That is why the
deletion count is so much larger than the rewrite count.
- Survivors are cut to the shortest form carrying something the code
does not already say. Restatement, preamble, hedging, changelog prose
and ASCII banners are gone; the non-obvious *why* stays.
- Preserved byte-identical: license headers, pragmas and directives
(`@ts-*`, `eslint-disable`, shebangs, `/// <reference>`), JSDoc tag
tokens, everything inside a string or template literal, and the sole
comment inside an otherwise empty block (ESLint `no-empty` counts a
comment-bearing block as non-empty).

## Verification

Every file passed two independent gates before being recorded as done:

1. `comments-only` — the required check.
2. A second comment-stripping comparator with a proper mode stack,
written for this pass because `comments-only`'s flat scanner desyncs on
nested template literals and quote-bearing regex literals and then
reports comment lines as code changes. Two files hit that false FAIL
(`utils/android-profiler/pipeline/index.ts`,
`scripts/extract-tools.mjs`); in both the "changed code" it printed was
literally `//` lines, and the second checker confirmed the code was
byte-identical.

After the last file, all 462 changed files were re-checked against
`main` with the same comparator, rather than trusting any agent's
self-report. **459 code-identical; 2 are non-code (`.svg`, `.md`); 1
intentional.**

The intentional one is `packages/argent/scripts/bundle-tools.cjs`: the
changed template literal *is* the comment header of the file it
generates, `packages/native-devtools-android/src/bundled-meta.ts`.
Fixing only the generated file would have been reverted by the next
build, so the generator changed too — and it has been verified to
reproduce the committed generated file byte-for-byte.

## Representative false claims removed

Not wording nits — statements a reader would have acted on:

- **Reversed directions.** `proxyStart`'s JSDoc had the tunnel backwards
(it is a reverse tunnel: the host binds first and the simulator dials
in). A `paste()` doc had the pasteboard copy direction reversed.
- **Contradicted by the code below it.** A timeout budget multiplied by
three where the probes run concurrently — the same comment said so six
lines later. A "warn once" that warns on every call. A "binary search"
that is a linear scan.
- **Named things that do not exist.** A `vega-fast-cli` binary, a
`finish-recording.ts`, a `publish-next.yml` workflow, two
`profiler-react19-*.md` design docs, a `DebuggerTarget.ts`, a commit
hash git does not know, two tool ids, an `ensureEnv` cycle.
- **Wrong by construction.** "Welford accumulators" across four files
where the code keeps naive `n`/`sum`/`sumSq`; `sum`/`sumSq` documented
over `actualDuration` when reduce sums `selfDuration`; a strict-mode
halving written `n/2` where the code ceils; field docs listing enum
values the producers never emit.
- **Guarantees the code does not make.** A validation matrix claiming to
cover "EVERY tool" that skips flagless ones; a Pareto cutoff that
`slice(0, 20)` makes inert; an idempotence claim where the real rule is
at-or-ahead; a capability note describing a clean 400 the shape-based
device resolver can never produce.
- **Unverifiable assertions** about prebuilt binaries, external CLIs and
the cloud SDK — deleted rather than kept as folklore, since nothing in
the repo can confirm them.
- **Stale numbers**: invented Android tool versions, hard-coded tool
counts and description lengths that had drifted.

## Review

A Fable agent reviewed both halves adversarially for over-deletion,
misread code, `no-empty` hazards and byte-identity violations.
Second-half verdict: **SHIP**, with two one-line restores, both applied
in the final commit — the `npm view ""` rationale behind a blank-token
guard, and the note that `argent-mcp` keeps a copy of
`SECRET_PLACEHOLDER_MARKER` it cannot import.

## Code issues surfaced but deliberately not fixed

This pass changes comments only. Eight genuine findings are logged for a
follow-up:

1. `telemetry/src/consent.ts` — a non-ENOENT read error returns null and
falls through to the default-on path, so file errors *can* silently flip
telemetry on.
2. `chromium-server/navigation.ts` — `navigate()` is reachable from
`POST /api/navigate` with only a `typeof === "string"` check; open-url's
schema is a bare `z.string()`, so the "already validated by zod" premise
never held.
3. `http.ts` — `constantTimeEqual` returns early on a length mismatch,
so the auth token's length is observable.
4. `describe/index.ts:~114` — the ios-remote branch passes `{ isTvOs:
false }` unconditionally, so a remote tvOS simulator takes the iOS
ax-service path, though `isRemoteTvOsSimulator` exists and shake/paste
do use it.
5. `devices/boot-device.ts` — `-crash-report-mode never` is passed
unconditionally *and* appended again by the feature-detecting path, so
every emulator spawn passes it twice.
6. `react-profiler/pipeline/04-rank.ts` — `PARETO_THRESHOLD_PCT` is
dead: `slice(0, 20)` always wins.
7. `reaped-sessions.ts` — a user-facing hint string tells the agent that
`react-profiler-start { force: true }` disposes the debugger and
profiler session; it does not. Left byte-identical because it is a
string literal, not a comment.
8. `utils/simctl-backend.ts` — `localSimctl` is exported with no
importers anywhere.

## Docs

No documentation change is needed: this pass touches only source
comments, and no user-facing capability, tool, CLI flag, config key or
flow-file behaviour changed.

---------

Co-authored-by: filip131311 <f.kaminski2000@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 12:16:20 +02:00

227 lines
9.6 KiB
JavaScript

#!/usr/bin/env node
// Fails if the packages under packages/* don't all share one version, or if the
// root server.json (the MCP registry manifest) has drifted from the version and
// npm coordinates of packages/argent, the package it points at. server.json is
// checked here because it is the one such file outside packages/*, so a workspace
// bump leaves it behind and the registry then rejects the publish.
import { readFileSync, readdirSync, realpathSync } from "node:fs";
import { dirname, join, relative } from "node:path";
import { fileURLToPath } from "node:url";
import { argv as processArgv } from "node:process";
const selfPath = realpathSync(fileURLToPath(import.meta.url));
const repoRoot = join(dirname(selfPath), "..");
const packagesDir = join(repoRoot, "packages");
const argentManifestPath = join(packagesDir, "argent", "package.json");
const serverJsonPath = join(repoRoot, "server.json");
// Not workspace members, so not part of the lockstep versioning: the docs site is
// excluded from the root `workspaces` glob (it keeps its own lockfile) and carries
// a standalone version that must not be read as drift.
const NON_WORKSPACE_DIRS = new Set(["docs"]);
/**
* Everything server.json has to keep in step with the npm package it points at,
* one line per problem. Kept free of fs reads so it can be unit-tested.
* @param {string | undefined} publishedVersion the version packages/argent/package.json carries
* @param {{ name?: string, mcpName?: string }} argentPkg packages/argent/package.json
* @param {{ name?: string, version?: string, packages?: { identifier?: string, version?: string }[] }} server server.json
* @returns {string[]} empty when in sync
*/
export function serverJsonMismatches(publishedVersion, argentPkg, server) {
const mismatches = [];
// Without it every comparison below would pass by matching undefined against
// undefined, so its absence is a mismatch in its own right.
if (!publishedVersion) {
mismatches.push(
"packages/argent/package.json has no version — nothing for server.json to be checked against"
);
} else if (server.version !== publishedVersion) {
mismatches.push(
`server.json version is ${server.version}, packages/argent/package.json is ${publishedVersion}`
);
}
// mcpName on the published tarball is what proves to the registry that the
// io.github.software-mansion namespace is ours, so its absence is as fatal as a
// mismatch — and an absent one equals an absent server.json name by ===.
if (!argentPkg.mcpName) {
mismatches.push(
"packages/argent/package.json has no mcpName — the registry reads it off the published tarball to prove the namespace"
);
} else if (server.name !== argentPkg.mcpName) {
mismatches.push(
`server.json name (${server.name}) != packages/argent/package.json mcpName (${argentPkg.mcpName})`
);
}
// Every packages[].identifier below is compared against this name, so without it
// those comparisons pass on undefined === undefined.
if (!argentPkg.name) {
mismatches.push(
"packages/argent/package.json has no name — server.json's identifier has nothing to point at"
);
}
// Absent, empty and non-array all leave the registry entry naming nothing to
// install, so they get one message rather than a TypeError out of the loop.
const packages = server.packages;
if (!Array.isArray(packages) || packages.length === 0) {
mismatches.push(
"server.json has no packages array — the registry entry would name nothing to install"
);
return mismatches;
}
for (const [i, pkg] of packages.entries()) {
// `null` would throw out of the loop; a scalar or an array reads as undefined
// on every field and would be reported as a pile of missing values rather than
// the wrong shape it is.
if (pkg === null || typeof pkg !== "object" || Array.isArray(pkg)) {
const shape = pkg === null ? "null" : Array.isArray(pkg) ? "an array" : typeof pkg;
mismatches.push(
`server.json packages[${i}] is ${shape}, not an object naming a registry and identifier`
);
continue;
}
if (pkg.version !== publishedVersion) {
mismatches.push(
`server.json packages[${i}].version is ${pkg.version}, packages/argent/package.json is ${publishedVersion}`
);
}
if (pkg.identifier !== argentPkg.name) {
mismatches.push(
`server.json packages[${i}].identifier (${pkg.identifier}) != published package name (${argentPkg.name})`
);
}
}
return mismatches;
}
/**
* Reads a tracked JSON file, reporting a missing or malformed one as a sentence
* instead of a raw stack trace.
* @param {string} path
* @returns {any}
*/
function readTrackedJson(path) {
const shown = relative(repoRoot, path);
let raw;
try {
raw = readFileSync(path, "utf8");
} catch (error) {
console.error(`Cannot read ${shown}: ${error.message}`);
console.error(`\nIt is tracked in git — restore it with \`git checkout -- ${shown}\`.`);
process.exit(1);
}
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
console.error(`${shown} is not valid JSON: ${error.message}`);
console.error(`\nIt is tracked in git — restore it with \`git checkout -- ${shown}\`.`);
process.exit(1);
}
// A scalar, null or an array parses cleanly and then either throws or compares
// equal to undefined on every field read. Both callers want an object.
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
const shape = parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed;
console.error(`${shown} is ${shape}, not a JSON object`);
console.error(`\nIt is tracked in git — restore it with \`git checkout -- ${shown}\`.`);
process.exit(1);
}
return parsed;
}
function main() {
const byVersion = new Map(); // version -> package names
// readdir order is filesystem-dependent; sorted so a scan that exits on a bad
// manifest stops at the same package on every machine.
const entries = readdirSync(packagesDir, { withFileTypes: true }).sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (NON_WORKSPACE_DIRS.has(entry.name)) continue;
const manifestPath = join(packagesDir, entry.name, "package.json");
let manifest;
try {
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
} catch (error) {
// No package.json at all means the directory is not a workspace package —
// an unchecked-out submodule, a stray build directory. A manifest that is
// there but unusable is different: skipping it would drop a real package
// out of the comparison and pass a workspace that drifted.
if (error.code === "ENOENT") continue;
console.error(`Cannot read packages/${entry.name}/package.json: ${error.message}`);
console.error(
`\nIt is tracked in git — restore it with \`git checkout -- ${relative(repoRoot, manifestPath)}\`.`
);
process.exit(1);
}
// `?.` would mask a manifest that parses to `null`, a bare scalar or an array:
// there but unusable, and none of them reach the catch above. Dropping one
// would take a real package out of the lockstep comparison.
if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) {
const shape =
manifest === null ? "null" : Array.isArray(manifest) ? "an array" : typeof manifest;
console.error(`packages/${entry.name}/package.json is ${shape}, not a JSON object`);
console.error(
`\nIt is tracked in git — restore it with \`git checkout -- ${relative(repoRoot, manifestPath)}\`.`
);
process.exit(1);
}
if (!manifest.version) continue;
const names = byVersion.get(manifest.version) ?? [];
names.push(manifest.name ?? entry.name);
byVersion.set(manifest.version, names);
}
let failed = false;
// Reported before the reads below, so a corrupt server.json can't swallow it.
if (byVersion.size > 1) {
console.error("Workspace package versions are out of sync:");
for (const [version, names] of [...byVersion].sort()) {
console.error(` ${version}: ${names.sort().join(", ")}`);
}
console.error(
"\nEvery package under packages/* must share one version. Bump the outliers to match.\n"
);
failed = true;
}
// An empty packages/ never gets past this read. It only proves the file parses,
// though — a missing version is caught as a mismatch below.
const argentPkg = readTrackedJson(argentManifestPath);
const server = readTrackedJson(serverJsonPath);
// Reported even when packages/* already disagree, so a half-finished bump
// surfaces every remaining edit in one run.
const mismatches = serverJsonMismatches(argentPkg.version, argentPkg, server);
if (mismatches.length > 0) {
console.error("server.json is out of sync with packages/argent/package.json:");
for (const line of mismatches) console.error(` ${line}`);
console.error(
"\nEvery line above starts with the file to edit. server.json is usually the one:\n" +
"it is not under packages/*, so a workspace bump sweeps past and leaves it behind."
);
failed = true;
}
if (failed) process.exit(1);
console.log(`All workspace packages and server.json are at ${argentPkg.version}.`);
}
// Run only when invoked directly, not when imported by the test. Both sides are
// realpath'd: node realpaths the main module before setting import.meta.url but
// leaves process.argv[1] as typed, so comparing them raw makes a path through a
// symlink skip main() and exit 0 with no output.
if (processArgv[1] && realpathSync(processArgv[1]) === selfPath) {
main();
}