Implement the 031-quality-gates-hygiene feature that strengthens automated quality gates by adding pnpm audit to the check script, v8 coverage thresholds with per-directory auto-ratchet (domain 96%, adapters 71%, persistence 87%), Biome cognitive complexity enforcement (max 15), and keyboard accessibility for the combatant row, while cleaning up all blanket biome-ignore comments, refactoring 5 overly complex functions into smaller helpers, and codifying the early-enforcement principle in the constitution and CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,76 @@ export function saveEncounter(encounter: Encounter): void {
|
||||
}
|
||||
}
|
||||
|
||||
function validateAc(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isInteger(value) && value >= 0
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function validateConditions(value: unknown): ConditionId[] | undefined {
|
||||
if (!Array.isArray(value)) return undefined;
|
||||
const valid = value.filter(
|
||||
(v): v is ConditionId =>
|
||||
typeof v === "string" && VALID_CONDITION_IDS.has(v),
|
||||
);
|
||||
return valid.length > 0 ? valid : undefined;
|
||||
}
|
||||
|
||||
function validateCreatureId(value: unknown) {
|
||||
return typeof value === "string" && value.length > 0
|
||||
? creatureId(value)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function validateHp(
|
||||
rawMaxHp: unknown,
|
||||
rawCurrentHp: unknown,
|
||||
): { maxHp: number; currentHp: number } | undefined {
|
||||
if (
|
||||
typeof rawMaxHp !== "number" ||
|
||||
!Number.isInteger(rawMaxHp) ||
|
||||
rawMaxHp < 1
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const validCurrentHp =
|
||||
typeof rawCurrentHp === "number" &&
|
||||
Number.isInteger(rawCurrentHp) &&
|
||||
rawCurrentHp >= 0 &&
|
||||
rawCurrentHp <= rawMaxHp;
|
||||
return {
|
||||
maxHp: rawMaxHp,
|
||||
currentHp: validCurrentHp ? rawCurrentHp : rawMaxHp,
|
||||
};
|
||||
}
|
||||
|
||||
function rehydrateCombatant(c: unknown) {
|
||||
const entry = c as Record<string, unknown>;
|
||||
const base = {
|
||||
id: combatantId(entry.id as string),
|
||||
name: entry.name as string,
|
||||
initiative:
|
||||
typeof entry.initiative === "number" ? entry.initiative : undefined,
|
||||
};
|
||||
|
||||
const shared = {
|
||||
...base,
|
||||
ac: validateAc(entry.ac),
|
||||
conditions: validateConditions(entry.conditions),
|
||||
isConcentrating: entry.isConcentrating === true ? true : undefined,
|
||||
creatureId: validateCreatureId(entry.creatureId),
|
||||
};
|
||||
|
||||
const hp = validateHp(entry.maxHp, entry.currentHp);
|
||||
return hp ? { ...shared, ...hp } : shared;
|
||||
}
|
||||
|
||||
function isValidCombatantEntry(c: unknown): boolean {
|
||||
if (typeof c !== "object" || c === null || Array.isArray(c)) return false;
|
||||
const entry = c as Record<string, unknown>;
|
||||
return typeof entry.id === "string" && typeof entry.name === "string";
|
||||
}
|
||||
|
||||
export function loadEncounter(): Encounter | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
@@ -45,82 +115,9 @@ export function loadEncounter(): Encounter | null {
|
||||
};
|
||||
}
|
||||
|
||||
for (const c of combatants) {
|
||||
if (typeof c !== "object" || c === null || Array.isArray(c)) return null;
|
||||
const entry = c as Record<string, unknown>;
|
||||
if (typeof entry.id !== "string") return null;
|
||||
if (typeof entry.name !== "string") return null;
|
||||
}
|
||||
if (!combatants.every(isValidCombatantEntry)) return null;
|
||||
|
||||
const rehydrated = combatants.map((c) => {
|
||||
const entry = c as Record<string, unknown>;
|
||||
const base = {
|
||||
id: combatantId(entry.id as string),
|
||||
name: entry.name as string,
|
||||
initiative:
|
||||
typeof entry.initiative === "number" ? entry.initiative : undefined,
|
||||
};
|
||||
|
||||
// Validate AC field
|
||||
const ac = entry.ac;
|
||||
const validAc =
|
||||
typeof ac === "number" && Number.isInteger(ac) && ac >= 0
|
||||
? ac
|
||||
: undefined;
|
||||
|
||||
// Validate conditions field
|
||||
const rawConditions = entry.conditions;
|
||||
const validConditions: ConditionId[] | undefined = Array.isArray(
|
||||
rawConditions,
|
||||
)
|
||||
? (rawConditions.filter(
|
||||
(v): v is ConditionId =>
|
||||
typeof v === "string" && VALID_CONDITION_IDS.has(v),
|
||||
) as ConditionId[])
|
||||
: undefined;
|
||||
const conditions =
|
||||
validConditions && validConditions.length > 0
|
||||
? validConditions
|
||||
: undefined;
|
||||
|
||||
// Validate isConcentrating field
|
||||
const isConcentrating = entry.isConcentrating === true ? true : undefined;
|
||||
|
||||
// Validate creatureId field
|
||||
const rawCreatureId = entry.creatureId;
|
||||
const validCreatureId =
|
||||
typeof rawCreatureId === "string" && rawCreatureId.length > 0
|
||||
? creatureId(rawCreatureId)
|
||||
: undefined;
|
||||
|
||||
// Validate and attach HP fields if valid
|
||||
const maxHp = entry.maxHp;
|
||||
const currentHp = entry.currentHp;
|
||||
if (typeof maxHp === "number" && Number.isInteger(maxHp) && maxHp >= 1) {
|
||||
const validCurrentHp =
|
||||
typeof currentHp === "number" &&
|
||||
Number.isInteger(currentHp) &&
|
||||
currentHp >= 0 &&
|
||||
currentHp <= maxHp;
|
||||
return {
|
||||
...base,
|
||||
ac: validAc,
|
||||
conditions,
|
||||
isConcentrating,
|
||||
creatureId: validCreatureId,
|
||||
maxHp,
|
||||
currentHp: validCurrentHp ? currentHp : maxHp,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...base,
|
||||
ac: validAc,
|
||||
conditions,
|
||||
isConcentrating,
|
||||
creatureId: validCreatureId,
|
||||
};
|
||||
});
|
||||
const rehydrated = combatants.map(rehydrateCombatant);
|
||||
|
||||
const result = createEncounter(
|
||||
rehydrated,
|
||||
|
||||
Reference in New Issue
Block a user