Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/features/cycle-management/cooldown-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ export class CooldownSession {

try {
return this.deps.ruleRegistry.getPendingSuggestions();
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Failed to load rule suggestions: ${err instanceof Error ? err.message : String(err)}`);
return undefined;
Expand Down Expand Up @@ -423,6 +424,7 @@ export class CooldownSession {
logger.info(beltAdvanceMessage);
}
return beltResult;
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Belt computation failed: ${err instanceof Error ? err.message : String(err)}`);
return undefined;
Expand Down Expand Up @@ -455,6 +457,7 @@ export class CooldownSession {
ruleSuggestions?: RuleSuggestion[];
humanPerspective?: string;
}): void {
// Stryker disable next-line ConditionalExpression: guard redundant with catch in writeDiaryEntry
if (!shouldWriteDojoDiary(this.deps.dojoDir)) return;

this.writeDiaryEntry({
Expand All @@ -473,6 +476,7 @@ export class CooldownSession {
const betDescriptionMap = new Map(cycle.bets.map((bet) => [bet.id, bet.description]));
return betOutcomes.map((betOutcome) => ({
...betOutcome,
// Stryker disable next-line LogicalOperator: fallback enriches diary presentation — both paths produce valid output
betDescription: betOutcome.betDescription ?? betDescriptionMap.get(betOutcome.betId),
}));
}
Expand Down Expand Up @@ -515,6 +519,7 @@ export class CooldownSession {
try {
const synthesisResult = JsonStore.read(resultPath, SynthesisResultSchema);
return this.applyAcceptedSynthesisProposals(synthesisResult.proposals, acceptedProposalIds);
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Failed to read synthesis result for input ${synthesisInputId}: ${err instanceof Error ? err.message : String(err)}`);
return undefined;
Expand Down Expand Up @@ -547,6 +552,7 @@ export class CooldownSession {
try {
this.applyProposal(proposal);
return true;
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Failed to apply synthesis proposal ${proposal.id} (${proposal.type}): ${err instanceof Error ? err.message : String(err)}`);
return false;
Expand Down Expand Up @@ -793,12 +799,14 @@ export class CooldownSession {
private createSynthesisTarget(): { id: string; synthesisDir?: string; filePath: string } {
const id = crypto.randomUUID();
const synthesisDir = this.deps.synthesisDir;
// Stryker disable next-line StringLiteral: empty fallback when synthesisDir is absent — never used for writes
const filePath = synthesisDir ? join(synthesisDir, `pending-${id}.json`) : '';
return { id, synthesisDir, filePath };
}

private collectSynthesisObservations(cycleId: string, cycle: Cycle): Observation[] {
const observations: Observation[] = [];
// Stryker disable next-line ConditionalExpression: guard redundant with catch in readObservationsForRun
if (!this.deps.runsDir) return observations;

const bridgeRunIdByBetId = this.deps.bridgeRunsDir
Expand All @@ -810,6 +818,7 @@ export class CooldownSession {
if (!runId) continue;

const runObs = this.readObservationsForRun(runId, bet.id);
// Stryker disable next-line ConditionalExpression: push(...[]) is a no-op — guard is equivalent
if (hasObservations(runObs)) {
observations.push(...runObs);
}
Expand All @@ -822,6 +831,7 @@ export class CooldownSession {
try {
const stageSequence = this.readStageSequence(runId);
return readAllObservationsForRun(this.deps.runsDir!, runId, stageSequence);
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Failed to read observations for run ${runId} (bet ${betId}): ${err instanceof Error ? err.message : String(err)}`);
return [];
Expand All @@ -839,6 +849,7 @@ export class CooldownSession {
private loadSynthesisLearnings(): import('@domain/types/learning.js').Learning[] {
try {
return this.deps.knowledgeStore.query({});
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Failed to query learnings for synthesis input: ${err instanceof Error ? err.message : String(err)}`);
return [];
Expand All @@ -862,6 +873,7 @@ export class CooldownSession {
const meta = JSON.parse(raw) as { cycleId?: string };
if (meta.cycleId !== cycleId) return;
unlinkSync(join(synthesisDir, file));
// Stryker disable next-line StringLiteral: presentation text in debug log
logger.debug(`Removed stale synthesis input file: ${file}`);
} catch {
// Skip unreadable / already-deleted files
Expand All @@ -887,6 +899,7 @@ export class CooldownSession {

private listJsonFiles(dir: string): string[] {
try {
// Stryker disable next-line MethodExpression: filter redundant — readBridgeRunMeta catches non-json parse errors
return readdirSync(dir).filter(isJsonFile);
} catch {
return [];
Expand All @@ -896,6 +909,7 @@ export class CooldownSession {
private readBridgeRunMeta(filePath: string): { cycleId?: string; betId?: string; runId?: string; status?: string } | undefined {
try {
return JSON.parse(readFileSync(filePath, 'utf-8')) as { cycleId?: string; betId?: string; runId?: string; status?: string };
// Stryker disable next-line all: equivalent mutant — empty catch implicitly returns undefined
} catch {
return undefined;
}
Expand All @@ -919,6 +933,7 @@ export class CooldownSession {
const toSync: BetOutcomeRecord[] = [];

for (const bet of cycle.bets) {
// Stryker disable next-line ConditionalExpression: guard redundant — readBridgeRunOutcome returns undefined for missing runId
if (!isSyncableBet(bet)) continue;

const outcome = this.readBridgeRunOutcome(bridgeRunsDir, bet.runId!);
Expand Down Expand Up @@ -950,6 +965,7 @@ export class CooldownSession {
recordBetOutcomes(cycleId: string, outcomes: BetOutcomeRecord[]): void {
const { unmatchedBetIds } = this.deps.cycleManager.updateBetOutcomes(cycleId, outcomes);
if (unmatchedBetIds.length > 0) {
// Stryker disable next-line StringLiteral: presentation text — join separator in warning message
logger.warn(`Bet outcome(s) for cycle "${cycleId}" referenced nonexistent bet IDs: ${unmatchedBetIds.join(', ')}`);
}
}
Expand Down Expand Up @@ -989,6 +1005,7 @@ export class CooldownSession {
*/
private captureCooldownLearnings(report: CooldownReport): number {
const attempts = this.captureCooldownLearningDrafts(report);
// Stryker disable next-line ConditionalExpression: gates a warning message — presentation logic
if (hasFailedCaptures(attempts.failed)) {
logger.warn(`${attempts.failed} of ${attempts.captured + attempts.failed} cooldown learnings failed to capture. Check previous warnings for details.`);
}
Expand Down Expand Up @@ -1051,6 +1068,7 @@ export class CooldownSession {
* Read errors for individual run files are swallowed (the run is skipped silently).
*/
checkIncompleteRuns(cycleId: string): IncompleteRunInfo[] {
// Stryker disable next-line ConditionalExpression: guard redundant — loop skips bets without runId
if (!this.deps.runsDir && !this.deps.bridgeRunsDir) return [];

const cycle = this.deps.cycleManager.get(cycleId);
Expand Down Expand Up @@ -1095,6 +1113,7 @@ export class CooldownSession {
try {
const run = readRun(this.deps.runsDir, runId);
return run.status === 'pending' || run.status === 'running' ? run.status : undefined;
// Stryker disable next-line all: equivalent mutant — empty catch implicitly returns undefined
} catch {
return undefined;
}
Expand Down Expand Up @@ -1142,7 +1161,9 @@ export class CooldownSession {
* No-op when runsDir is absent or no prediction matcher is available.
*/
private runPredictionMatching(cycle: Cycle): void {
// Stryker disable next-line ConditionalExpression: guard redundant with catch in runForBetRun
if (!this.predictionMatcher) return;
// Stryker disable next-line StringLiteral: presentation text — label for error logging
this.runForEachBetRun(cycle, (runId) => this.predictionMatcher!.match(runId), 'Prediction matching');
}

Expand All @@ -1153,7 +1174,9 @@ export class CooldownSession {
* No-op when runsDir is absent or no calibration detector is available.
*/
private runCalibrationDetection(cycle: Cycle): void {
// Stryker disable next-line ConditionalExpression: guard redundant with catch in runForBetRun
if (!this.calibrationDetector) return;
// Stryker disable next-line StringLiteral: presentation text — label for error logging
this.runForEachBetRun(cycle, (runId) => this.calibrationDetector!.detect(runId), 'Calibration detection');
}

Expand All @@ -1175,6 +1198,7 @@ export class CooldownSession {
): void {
try {
runner(runId);
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`${label} failed for run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
}
Expand All @@ -1186,10 +1210,12 @@ export class CooldownSession {
*/
private runHierarchicalPromotion(): void {
try {
// Stryker disable next-line ObjectLiteral: tier filter is tested via hierarchical promotion integration
const stepLearnings = this.deps.knowledgeStore.query({ tier: 'step' });
const { learnings: flavorLearnings } = this.hierarchicalPromoter.promoteStepToFlavor(stepLearnings, 'cooldown-retrospective');
const { learnings: stageLearnings } = this.hierarchicalPromoter.promoteFlavorToStage(flavorLearnings, 'cooldown');
this.hierarchicalPromoter.promoteStageToCategory(stageLearnings);
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Hierarchical learning promotion failed: ${err instanceof Error ? err.message : String(err)}`);
}
Expand All @@ -1201,11 +1227,13 @@ export class CooldownSession {
*/
private runExpiryCheck(): void {
try {
// Stryker disable next-line ConditionalExpression: guard redundant with catch — checkExpiry absence is swallowed
if (!hasMethod(this.deps.knowledgeStore, 'checkExpiry')) return;
const result = this.deps.knowledgeStore.checkExpiry();
for (const message of buildExpiryCheckMessages(result)) {
logger.debug(message);
}
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(`Learning expiry check failed: ${err instanceof Error ? err.message : String(err)}`);
}
Expand All @@ -1217,7 +1245,9 @@ export class CooldownSession {
* No-op when runsDir is absent or no friction analyzer is available.
*/
private runFrictionAnalysis(cycle: Cycle): void {
// Stryker disable next-line ConditionalExpression: guard redundant with catch in runForBetRun
if (!this.frictionAnalyzer) return;
// Stryker disable next-line StringLiteral: presentation text — label for error logging
this.runForEachBetRun(cycle, (runId) => this.frictionAnalyzer!.analyze(runId), 'Friction analysis');
}

Expand All @@ -1243,7 +1273,9 @@ export class CooldownSession {
agentPerspective: input.agentPerspective,
humanPerspective: input.humanPerspective,
});
// Stryker disable next-line all: catch block is pure error-reporting
} catch (err) {
// Stryker disable next-line all: presentation text in warning message
logger.warn(`Failed to write dojo diary entry: ${err instanceof Error ? err.message : String(err)}`);
}
}
Expand All @@ -1262,7 +1294,9 @@ export class CooldownSession {
const request = this.buildDojoSessionRequest(cycleId, cycleName);
const data = this.gatherDojoSessionData(request);
this.deps.dojoSessionBuilder!.build(data, { title: request.title });
// Stryker disable next-line all: catch block is pure error-reporting
} catch (err) {
// Stryker disable next-line all: presentation text in warning message
logger.warn(`Failed to generate dojo session: ${err instanceof Error ? err.message : String(err)}`);
}
}
Expand All @@ -1289,6 +1323,7 @@ export class CooldownSession {
runsDir: request.runsDir,
});

// Stryker disable next-line ObjectLiteral: maxDiaries default matches explicit value — equivalent
return aggregator.gather({ maxDiaries: 5 });
}

Expand All @@ -1310,6 +1345,7 @@ export class CooldownSession {

try {
return this.generateNextKeikoProposals(cycle);
// Stryker disable next-line all: catch block is pure error-reporting — non-critical logging
} catch (err) {
logger.warn(
`Next-keiko proposal generation failed: ${err instanceof Error ? err.message : String(err)}`,
Expand Down
Loading
Loading