Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
172 lines
6.2 KiB
JavaScript
172 lines
6.2 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* Property tests for the compact MSD-state status normalizer (#2162).
|
||
*
|
||
* shortMsdStatus() collapses free-text STATE.md statuses to a canonical
|
||
* keyword (or a capped first-word fallback). As a parsing/normalization
|
||
* contract it gets property coverage per the repo testing rules, alongside
|
||
* the example-based cases in msd-statusline.test.cjs.
|
||
*/
|
||
|
||
const { test, describe } = require('node:test');
|
||
const fc = require('./helpers/fast-check-setup.cjs');
|
||
|
||
const { shortMsdStatus } = require('../hooks/msd-statusline.js');
|
||
|
||
const CANONICAL = ['discussing', 'planning', 'executing', 'verifying', 'completed', 'paused'];
|
||
|
||
describe('shortMsdStatus properties (#2162)', () => {
|
||
test('totality: any string input yields null or a short non-empty string', () => {
|
||
fc.assert(
|
||
fc.property(fc.string(), (s) => {
|
||
const out = shortMsdStatus(s);
|
||
if (out === null) return true;
|
||
return typeof out === 'string' && out.length > 0 && out.length <= 16;
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('canonical statuses are fixed points (paused shouts as PAUSED)', () => {
|
||
fc.assert(
|
||
fc.property(fc.constantFrom(...CANONICAL), (canonical) => {
|
||
const out = shortMsdStatus(canonical);
|
||
return canonical === 'paused' ? out === 'PAUSED' : out === canonical;
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('output never contains whitespace or separator characters', () => {
|
||
// The compact line joins segments with ' · ' — a status containing
|
||
// whitespace or the separator would corrupt the segment structure.
|
||
fc.assert(
|
||
fc.property(fc.string(), (s) => {
|
||
const out = shortMsdStatus(s);
|
||
return out === null || !/[\s·—–]/.test(out);
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('unrecognized free text falls back to its first word, capped at 16', () => {
|
||
// Alphabetic words that are not canonical and don't contain canonical
|
||
// trigger substrings exercise the fallback path deterministically.
|
||
const word = fc.stringMatching(/^[A-Za-z]{1,32}$/).filter((w) => {
|
||
const lower = w.toLowerCase();
|
||
return !CANONICAL.some((c) => lower.includes(c.slice(0, 4)));
|
||
});
|
||
fc.assert(
|
||
fc.property(word, word, (first, second) => {
|
||
const out = shortMsdStatus(`${first} ${second}`);
|
||
// The normalizer may still map some phrasings to a canonical keyword
|
||
// (e.g. synonym tables); otherwise the first word survives, capped.
|
||
return out === null || CANONICAL.concat('PAUSED').includes(out) || out === first.slice(0, 16);
|
||
}),
|
||
);
|
||
});
|
||
});
|
||
|
||
/**
|
||
* Property tests for STATE.md freshness marker derivation (#2734).
|
||
*
|
||
* These properties are asserted against `deriveStateFreshness`,
|
||
* `isValidStateHeadStamp`, `formatStateFreshness`, `formatMsdState`, and
|
||
* `formatMsdStateCompact` per `.msd/phase/feat-2734-statusline-state-freshness/50-test-matrix.md`
|
||
* rows P1-P5. None of these symbols exist on `hooks/msd-statusline.js` yet —
|
||
* this block is deliberately failing-first TDD.
|
||
*/
|
||
const {
|
||
STATE_HEAD_ADVISORY_COMMITS,
|
||
isValidStateHeadStamp,
|
||
deriveStateFreshness,
|
||
formatStateFreshness,
|
||
formatMsdState: formatMsdStateFull,
|
||
formatMsdStateCompact: formatMsdStateCompactFn,
|
||
} = require('../hooks/msd-statusline.js');
|
||
|
||
describe('state-head freshness properties (#2734)', () => {
|
||
test('derivationIsTotalOverArbitraryStamps', () => {
|
||
const root = require('node:os').tmpdir();
|
||
fc.assert(
|
||
fc.property(fc.string(), (stamp) => {
|
||
const ir = deriveStateFreshness(root, stamp);
|
||
if (!ir || typeof ir !== 'object') return false;
|
||
const keys = Object.keys(ir).sort().join(',');
|
||
if (keys !== ['commit_stale', 'commits_behind', 'state_head'].sort().join(',')) return false;
|
||
const cb = ir.commits_behind;
|
||
return cb === null || (Number.isInteger(cb) && cb >= 0);
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('fenceAcceptsOnlyHexInRange', () => {
|
||
fc.assert(
|
||
fc.property(fc.string(), (s) => {
|
||
const expected = /^[0-9a-f]{4,40}$/i.test(String(s).trim());
|
||
return isValidStateHeadStamp(s) === expected;
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('markerVisibilityIsMonotonicInCount', () => {
|
||
fc.assert(
|
||
fc.property(fc.nat({ max: 1000000 }), (n) => {
|
||
const out = formatStateFreshness({ commits_behind: n });
|
||
const shouldShow = n >= STATE_HEAD_ADVISORY_COMMITS;
|
||
return shouldShow ? out !== '' : out === '';
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('unknownNeverDegradesToFresh', () => {
|
||
const root = require('node:os').tmpdir();
|
||
fc.assert(
|
||
fc.property(fc.string(), (stamp) => {
|
||
const ir = deriveStateFreshness(root, stamp);
|
||
return ir.commit_stale === null;
|
||
}),
|
||
);
|
||
});
|
||
|
||
test('renderersAreTotalOverArbitraryIr', () => {
|
||
const optionalString = fc.oneof(fc.constant(undefined), fc.string());
|
||
const optionalCount = fc.oneof(fc.constant(undefined), fc.nat({ max: 999 }));
|
||
|
||
const hasBadSeparator = (str) => {
|
||
const trimmed = str.trim();
|
||
if (trimmed.startsWith('·') || trimmed.endsWith('·')) return true;
|
||
return str.includes('··') || str.includes('· ·');
|
||
};
|
||
|
||
fc.assert(
|
||
fc.property(
|
||
fc.record({
|
||
status: optionalString,
|
||
phaseNum: optionalCount,
|
||
phaseTotal: optionalCount,
|
||
phaseName: optionalString,
|
||
milestone: optionalString,
|
||
milestoneName: optionalString,
|
||
percent: fc.oneof(fc.constant(undefined), fc.nat({ max: 100 })),
|
||
activePhase: optionalString,
|
||
nextAction: optionalString,
|
||
nextPhases: fc.oneof(fc.constant(undefined), fc.array(fc.string(), { maxLength: 3 })),
|
||
completedPhases: optionalCount,
|
||
totalPhases: optionalCount,
|
||
noActiveWorkstream: fc.boolean(),
|
||
freshness: fc.record({
|
||
state_head: fc.oneof(fc.constant(null), fc.string()),
|
||
commits_behind: fc.oneof(fc.constant(null), fc.nat({ max: 1000000 })),
|
||
commit_stale: fc.oneof(fc.constant(null), fc.boolean()),
|
||
}),
|
||
}),
|
||
(s) => {
|
||
const full = formatMsdStateFull(s);
|
||
const compact = formatMsdStateCompactFn(s);
|
||
if (typeof full !== 'string' || typeof compact !== 'string') return false;
|
||
return !hasBadSeparator(full) && !hasBadSeparator(compact);
|
||
},
|
||
),
|
||
);
|
||
});
|
||
});
|