* test(#4135): regression rows for pristine regen coverage collapse RED skeleton: src/pristine-baseline.cts exports findPristineInGit as a null-returning stub (wired into verifyFile after the #4145 orphan tier, behavior-neutral) so the git-history rows fail behaviorally, not at require time. Failing-first rows: baseline_covered aggregate on a 1-of-13 multi-version fixture, coverageHeadline typed renderer, the opt-in --min-baseline-coverage gate (exit 3, >= threshold semantics, vacuous-pass and malformed-value boundaries), git-history baseline recovery (dropped-line catch + surviving-line verify + older-commit hop), findPristineInGit unit, Step 5a workflow headline contract, and the installer-side describeBaselineCoverage honest N-of-M summary with the collapse disk-state pinned. Negative-space rows pin today: non-git ok_no_baseline posture, no-match-no-adoption, #3657 drift never rescued, canonical precedence, and no git tier without --pristine-dir. * fix(#4135): headline baseline coverage, opt-in strict gate, git-history widening The #3407 promotion rule regenerates gsd-pristine/ baselines from the INCOMING release source and keeps only candidates byte-identical with the OUTGOING recorded hash — correct in isolation, but on a multi-version jump the surviving set is precisely the files upstream did NOT change. The verifier then reports ok_no_baseline (advisory, exit 0) for everything else, and no surface distinguishes a 12-of-13-unverified green run from a fully-verified one: the human summary printed Checked/Failures only, the JSON had no coverage aggregate, and the installer's update output gave per-bucket counts without N-of-M framing. All three issue directions, none exclusive: - Report coverage prominently: --json gains an additive baseline_covered aggregate; the human summary leads with 'Baseline coverage: N of M file(s)...' on every run plus an advisory section naming each skipped file and reason; the installer prints an honest covered-of-modified line via the exported describeBaselineCoverage helper (typed return, exact contract); workflow Step 5a computes and prints the headline before any pass/fail framing. - Fail louder on low coverage: opt-in --min-baseline-coverage <0..1> exits with new documented code 3 when coverage falls below the threshold (>= semantics; empty run vacuously passes; content failure exit 1 outranks it; malformed values are usage errors, exit 2). Default posture unchanged — no_baseline stays advisory per #934. - Widen the promotion rule (its only trustworthy form): when no baseline resolves under gsd-pristine/ and a hash is recorded, the verifier now recovers the baseline from the config dir's own git history — the workflow's documented Option A — anchored by the same authority every tier trusts, exact pristine_hashes sha-256 equality. Read-only (git log/git show, windowsHide per #685), bounded (100 commits/file, 10s/subprocess), null-on-any-failure so ok_no_baseline remains the universal fallback. Tier order: canonical join -> #4145 orphan scan -> git history -> OK_NO_BASELINE; #3657 drift and canonical precedence untouched. Hash validation in saveLocalPatches is NOT relaxed — the collapse is legitimate conservatism; hiding it was the bug. Measured on the issue's shape (13 files, 12 changed upstream, 1.10->1.12): non-git installs report baseline_covered 1/13 with the headline and can gate at exit 3; a git-managed config dir with the outgoing bytes in history verifies 13/13. Review fixes folded in: workflow headline derives the unverified count from checked - baseline_covered (not the drift+no_baseline sum), and the new site-scoped allow-test-rule annotation carries its ADR-456 see-ref on the marker line. Emitted-Drift-Ack-Growth: reapply-patches.md — #4135 — +20 lines / ~1.5 KB, prose and bash only: two additive parse lines (BASELINE_COVERED, CHECKED_COUNT), a Step 5a coverage-headline block printed BEFORE any pass/fail statement (documents the opt-in --min-baseline-coverage exit-3 gate), and one Option B sentence noting the verifier's read-only git-history fallback. No step ordering, gate, tool-invocation, or dispatch shape changed; 5a's fail/drift/advisory handling is unchanged, the headline only precedes it. * chore(#4135): backfill PR number into changeset fragment --------- Co-authored-by: agent-4135 <agent-4135@gsd.local>
This commit is contained in:
@@ -4237,3 +4237,144 @@ describe('Bug #4145: saveLocalPatches rescues hash-matching orphaned pristine sn
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded regression block — #4135 (installer side). saveLocalPatches'
|
||||
// hash-validated regeneration (the #3407 promotion rule) keeps only
|
||||
// candidates byte-identical across the whole version span, so a
|
||||
// multi-version update leaves gsd-pristine/ holding near-zero baselines —
|
||||
// and the update output never says so in N-of-M terms. The fix keeps the
|
||||
// hash validation untouched (disk behavior is pinned here) and adds an
|
||||
// honest coverage summary via an exported typed helper.
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe('folded:bug-4135-saveLocalPatches-coverage-line', () => {
|
||||
'use strict';
|
||||
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
|
||||
const { test, describe, beforeEach } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const INSTALL = require(path.join(ROOT, 'bin', 'install.js'));
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
|
||||
const MANIFEST_NAME = 'gsd-file-manifest.json';
|
||||
|
||||
function sha256(content) {
|
||||
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
||||
}
|
||||
|
||||
function countFiles(dir) {
|
||||
let n = 0;
|
||||
if (!fs.existsSync(dir)) return 0;
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) n += countFiles(path.join(dir, entry.name));
|
||||
else if (entry.isFile()) n += 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
describe('Bug #4135: saveLocalPatches reports honest gsd-pristine coverage on multi-version updates', () => {
|
||||
let tmpDir;
|
||||
let configDir;
|
||||
let newSrcDir;
|
||||
let pristineDir;
|
||||
|
||||
beforeEach((t) => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-4135-cov-'));
|
||||
configDir = path.join(tmpDir, 'config');
|
||||
newSrcDir = path.join(tmpDir, 'new-release-src');
|
||||
pristineDir = path.join(configDir, 'gsd-pristine');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
fs.mkdirSync(newSrcDir, { recursive: true });
|
||||
t.after(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Seeds a multi-version-update fixture: `total` modified files, of which
|
||||
* `identical` are byte-identical across the span (the only regenerable
|
||||
* baselines) and the rest changed upstream in the incoming source.
|
||||
*/
|
||||
function seedMultiVersionFixture(total, identical) {
|
||||
const manifestFiles = {};
|
||||
for (let i = 1; i <= total; i++) {
|
||||
const rel = `gsd-core/workflows/flow-${String(i).padStart(2, '0')}.md`;
|
||||
const pristine =
|
||||
`# Flow ${i}\nStock content of the outgoing release for file ${i}.\n` +
|
||||
`Second outgoing stock line ${i} with plenty of substance.\n`;
|
||||
const user = pristine + `## User customisation ${i}\nCustom section on top of the outgoing release.\n`;
|
||||
const incoming = (i <= identical)
|
||||
? pristine
|
||||
: `# Flow ${i} (rewritten)\nIncoming release rewrote file ${i} across the span.\n`;
|
||||
manifestFiles[rel] = sha256(pristine);
|
||||
fs.mkdirSync(path.dirname(path.join(configDir, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(configDir, rel), user);
|
||||
fs.mkdirSync(path.dirname(path.join(newSrcDir, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(newSrcDir, rel), incoming);
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(configDir, MANIFEST_NAME),
|
||||
JSON.stringify({ version: '1.10.0', timestamp: '2026-08-01T00:00:00Z', runtime: 'claude', scope: 'global', files: manifestFiles }, null, 2),
|
||||
);
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core installer regression: the multi-version collapse itself is pinned
|
||||
* (hash validation untouched — only the byte-identical file survives),
|
||||
* and the honest N-of-M summary is available via the typed helper.
|
||||
*/
|
||||
test('#4135: saveLocalPatches multi-version regen keeps hash validation and reports 1-of-13 coverage', () => {
|
||||
const total = seedMultiVersionFixture(13, 1);
|
||||
|
||||
INSTALL.saveLocalPatches(configDir, {
|
||||
packageSrc: newSrcDir, runtime: 'claude', pathPrefix: '$HOME/.claude/', isGlobal: true,
|
||||
});
|
||||
|
||||
const covered = countFiles(pristineDir);
|
||||
assert.equal(covered, 1,
|
||||
'the collapse is pinned: only the byte-identical file survives hash-validated regeneration');
|
||||
assert.equal(typeof INSTALL.describeBaselineCoverage, 'function',
|
||||
'the coverage summary must be rendered by an exported typed helper');
|
||||
const summary = INSTALL.describeBaselineCoverage(total, covered);
|
||||
assert.equal(summary.complete, false);
|
||||
assert.equal(summary.uncovered, 12);
|
||||
assert.equal(
|
||||
summary.text,
|
||||
'gsd-pristine/ baselines cover 1 of 13 modified file(s) — 12 will be reported no_baseline by the reapply verifier',
|
||||
'partial coverage states the N-of-M collapse and its downstream effect',
|
||||
);
|
||||
});
|
||||
|
||||
/** Positive boundary: every modified file covered renders a complete summary. */
|
||||
test('#4135: describeBaselineCoverage reports complete when every modified file is covered', () => {
|
||||
const total = seedMultiVersionFixture(3, 3);
|
||||
|
||||
INSTALL.saveLocalPatches(configDir, {
|
||||
packageSrc: newSrcDir, runtime: 'claude', pathPrefix: '$HOME/.claude/', isGlobal: true,
|
||||
});
|
||||
|
||||
const covered = countFiles(pristineDir);
|
||||
assert.equal(covered, 3, 'a fully byte-identical span regenerates every baseline');
|
||||
const summary = INSTALL.describeBaselineCoverage(total, covered);
|
||||
assert.equal(summary.complete, true);
|
||||
assert.equal(summary.uncovered, 0);
|
||||
assert.equal(
|
||||
summary.text,
|
||||
'gsd-pristine/ baselines cover 3 of 3 modified file(s)',
|
||||
'complete coverage renders without a collapse tail',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user