Files
agent-skills/scripts/floor-guard-reference-test.js
T
mavericksea-ai 23f205295a fix(constraints): pair floor-guard thresholds by direction and report removed ones
Review on #600 (@abhisheksharma2411) found three cases in the threshold
comparison:

- Deleting one threshold from a rule that keeps others was silent: pairing
  was by position, and a threshold with no counterpart after the edit was
  skipped. It is now reported as threshold-removed.
- A number inserted earlier in a rule shifted the pairing, so "per ADR 7,
  >= 80%" compared 80 against 7 and reported a loosened minimum. Numbers are
  now paired within their direction (minimum with minimum, maximum with
  maximum, undirected with undirected); an added number tightens and stays
  silent.
- A renamed rule is still reported as rule-removed, since the key is the
  label and blocking is the safe default, and the output now says the label
  may have changed.

The review's cases are in the test, plus a control that adding a threshold
is silent: 21 pass, and the three review cases fail on the previous commit.
2026-09-24 02:26:03 +00:00

202 lines
7.4 KiB
JavaScript

#!/usr/bin/env node
'use strict';
// Contract test for skills/constraint-driven-development/references/floor-guard.md.
// The fenced `js` block is extracted unchanged and run, with real git, against fourteen
// small fixtures: each is a fresh repository with one source file, one test file and a
// CONSTRAINTS.md carrying a floor bullet, a coverage minimum, a bundle maximum and an
// Exceptions table. Loosening moves must exit 1, tightening and no-op moves must exit 0.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { after, before, test } = require('node:test');
const REFERENCE = path.join(
__dirname, '..', 'skills', 'constraint-driven-development', 'references', 'floor-guard.md',
);
const CONSTRAINTS = [
'# Constraints',
'',
'Last reviewed: 2026-08-08 by @owner',
'',
'## Floor',
'- No skipped tests.',
'- Latency: p95 under 200 ms and p99 under 900 ms.',
'',
'| Dimension | Rule |',
'|---|---|',
'| Coverage | >= 80% |',
'| Bundle | <= 200 KB |',
'',
'## Exceptions',
'',
'| ID | Rule | Owner | Expires |',
'|---|---|---|---|',
'| W1 | no-explicit-any | @owner | 2026-11-01 |',
'',
].join('\n');
let scratch;
let guard;
const sandboxes = [];
before(() => {
scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-floor-guard-test-'));
const source = fs.readFileSync(REFERENCE, 'utf8');
const match = source.match(/```js\n([\s\S]*?)\n```/);
assert.ok(match, 'floor-guard.md must contain one fenced js block');
guard = path.join(scratch, 'floor-guard.mjs');
fs.writeFileSync(guard, match[1] + '\n');
});
after(() => {
for (const dir of sandboxes) fs.rmSync(dir, { recursive: true, force: true });
if (scratch) fs.rmSync(scratch, { recursive: true, force: true });
});
function git(cwd, ...args) {
const result = spawnSync('git', args, {
cwd,
encoding: 'utf8',
env: {
...process.env,
GIT_AUTHOR_NAME: 'Floor Guard Test',
GIT_AUTHOR_EMAIL: 'floor-guard@example.invalid',
GIT_COMMITTER_NAME: 'Floor Guard Test',
GIT_COMMITTER_EMAIL: 'floor-guard@example.invalid',
GIT_CONFIG_GLOBAL: '/dev/null',
},
});
assert.equal(result.status, 0, `git ${args.join(' ')} failed: ${result.stderr}`);
return result.stdout;
}
function makeRepo() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-floor-guard-fixture-'));
sandboxes.push(root);
git(root, 'init', '-q');
fs.writeFileSync(path.join(root, 'app.js'), 'module.exports = 1;\n');
fs.writeFileSync(
path.join(root, 'app.test.js'),
"const assert = require('assert');\nassert.equal(require('./app'), 1);\n",
);
fs.writeFileSync(path.join(root, 'CONSTRAINTS.md'), CONSTRAINTS);
git(root, 'add', '.');
git(root, 'commit', '-qm', 'baseline');
return root;
}
function editConstraints(root, from, to) {
const file = path.join(root, 'CONSTRAINTS.md');
const text = fs.readFileSync(file, 'utf8');
assert.ok(text.includes(from), `fixture CONSTRAINTS.md must contain ${JSON.stringify(from)}`);
fs.writeFileSync(file, text.replace(from, to));
}
function runGuard(root) {
return spawnSync(process.execPath, [guard, '--base', 'HEAD'], { cwd: root, encoding: 'utf8' });
}
const cases = [
['unchanged repository is clean', 0, () => {}],
['a suppression added to a tracked file is a violation', 1, (root) => {
fs.writeFileSync(path.join(root, 'app.js'), '// @ts-ignore\nmodule.exports = 1;\n');
}],
['a suppression in a new untracked file is a violation', 1, (root) => {
fs.writeFileSync(path.join(root, 'new.js'), '// @ts-ignore\nmodule.exports = 1;\n');
}],
['a skipped test in a new untracked file is a violation', 1, (root) => {
fs.writeFileSync(path.join(root, 'new.test.js'), "test.skip('regression', () => {});\n");
}],
['a suppression in a staged new file is a violation', 1, (root) => {
fs.writeFileSync(path.join(root, 'new.js'), '// @ts-ignore\nmodule.exports = 1;\n');
git(root, 'add', '-A');
}],
['a deleted test file is a violation', 1, (root) => {
fs.unlinkSync(path.join(root, 'app.test.js'));
}],
['a staged deleted test file is a violation', 1, (root) => {
fs.unlinkSync(path.join(root, 'app.test.js'));
git(root, 'add', '-A');
}],
['an assertion removed from a surviving test file is a violation', 1, (root) => {
fs.writeFileSync(path.join(root, 'app.test.js'), "const assert = require('assert');\n");
}],
['lowering a minimum is a violation', 1, (root) => {
editConstraints(root, '| Coverage | >= 80% |', '| Coverage | >= 60% |');
}],
['raising a minimum is silent', 0, (root) => {
editConstraints(root, '| Coverage | >= 80% |', '| Coverage | >= 90% |');
}],
['raising a maximum is a violation', 1, (root) => {
editConstraints(root, '| Bundle | <= 200 KB |', '| Bundle | <= 300 KB |');
}],
['lowering a maximum is silent', 0, (root) => {
editConstraints(root, '| Bundle | <= 200 KB |', '| Bundle | <= 150 KB |');
}],
['deleting one threshold from a rule is a violation', 1, (root) => {
editConstraints(root, 'p95 under 200 ms and p99 under 900 ms.', 'p95 under 200 ms.');
}],
['a number inserted before a threshold is silent', 0, (root) => {
editConstraints(root, '| Coverage | >= 80% |', '| Coverage | per ADR 7, >= 80% |');
}],
['adding a threshold to a rule is silent', 0, (root) => {
editConstraints(root, '| Bundle | <= 200 KB |', '| Bundle | <= 200 KB, <= 60 KB gzipped |');
}],
['a deleted floor rule is a violation', 1, (root) => {
editConstraints(root, '- No skipped tests.\n', '');
}],
['a new exception row is a violation', 1, (root) => {
const file = path.join(root, 'CONSTRAINTS.md');
fs.appendFileSync(file, '| W2 | temporary waiver | @owner | 2026-10-01 |\n');
}],
];
for (const [name, expectedExit, mutate] of cases) {
test(`floor guard: ${name}`, () => {
const root = makeRepo();
mutate(root);
const result = runGuard(root);
assert.equal(
result.status, expectedExit,
`expected exit ${expectedExit}, got ${result.status}\nstdout: ${result.stdout}\nstderr: ${result.stderr}`,
);
});
}
test('floor guard: a removed exception row is silent', () => {
const root = makeRepo();
editConstraints(root, '| W1 | no-explicit-any | @owner | 2026-11-01 |\n', '');
const result = runGuard(root);
assert.equal(result.status, 0, result.stderr);
});
test('floor guard: a missing merge base exits 2, never 0', () => {
const root = makeRepo();
const result = spawnSync(process.execPath, [guard, '--base', 'no-such-ref'], {
cwd: root, encoding: 'utf8',
});
assert.equal(result.status, 2, result.stderr);
});
test('floor guard: a threshold with no readable direction is reported when it changes', () => {
const root = makeRepo();
editConstraints(root, '| W1 | no-explicit-any | @owner | 2026-11-01 |', '| W1 | no-explicit-any | @owner | 2027-11-01 |');
const result = runGuard(root);
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /threshold-changed/);
});
test('floor guard: a renamed rule is reported as removed, with a note that its label may have changed', () => {
const root = makeRepo();
editConstraints(root, '| Coverage | >= 80% |', '| Code coverage | >= 80% |');
const result = runGuard(root);
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /\[rule-removed\]/);
assert.match(result.stderr, /label changed/);
});