refactor: eliminate ~400 no-explicit-any warnings across the workspace

Parallel subagent pass: four typescript-pro agents on non-overlapping scopes.

Patterns applied:
- catch (err: any) { ... err.message ... } → catch (err) { ... getErrorMessage(err) ... }
  using the new @fusion/core helper. Bare catch {} where the error was unused.
- SQLite row types: defined typed XxxRow interfaces per table and cast
  .all()/.get() results via `as unknown as XxxRow[]` (the double cast is
  required because better-sqlite3 returns Record<string, SQLOutputValue>).
- rowToX(row: any) converters: typed argument with the matching row interface.
- Dynamic settings key writes: (settings as Record<string, unknown>)[key].
- React event handlers and setState callbacks: inferred types or concrete
  React.{Mouse,Change,Form}Event<...> where needed.
- pi-claude-cli: local PiMessage / PiContext duck types to avoid re-typing
  pi-ai concrete shapes; typed Claude stream event message fields.

72 files changed, ~400 anys eliminated. Typecheck passes across the workspace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-23 18:57:31 -07:00
parent d1bd02b2c9
commit 4cabe7f613
72 changed files with 1298 additions and 783 deletions

View File

@@ -42,6 +42,20 @@ import type { TaskStore } from "./store.js";
import { computeAccessState } from "./agent-permissions.js";
import { Database } from "./db.js";
/** Database row shape returned by SELECT on agentRatings. */
interface AgentRatingRow {
id: string;
agentId: string;
raterType: string;
raterId: string | null;
score: number;
category: string | null;
comment: string | null;
runId: string | null;
taskId: string | null;
createdAt: string;
}
/** Events emitted by AgentStore */
export interface AgentStoreEvents {
/** Emitted when an agent is created */
@@ -533,11 +547,11 @@ export class AgentStore extends EventEmitter {
};
}
private mapRatingRow(row: any): AgentRating {
private mapRatingRow(row: AgentRatingRow): AgentRating {
return {
id: row.id,
agentId: row.agentId,
raterType: row.raterType,
raterType: row.raterType as AgentRating["raterType"],
raterId: row.raterId ?? undefined,
score: row.score,
category: row.category ?? undefined,
@@ -604,7 +618,7 @@ export class AgentStore extends EventEmitter {
params.push(options.limit);
}
const rows = this.db.prepare(query).all(...params);
const rows = this.db.prepare(query).all(...params) as unknown as AgentRatingRow[];
return rows.map((row) => this.mapRatingRow(row));
}

View File

@@ -19,6 +19,27 @@ export interface AutomationStoreEvents {
"schedule:run": [data: { schedule: ScheduledTask; result: AutomationRunResult }];
}
/** Database row shape for the automations table. */
interface ScheduleRow {
id: string;
name: string;
description: string | null;
scheduleType: string;
cronExpression: string;
command: string;
enabled: number;
timeoutMs: number | null;
steps: string | null;
nextRunAt: string | null;
lastRunAt: string | null;
lastRunResult: string | null;
runCount: number;
runHistory: string;
scope: string;
createdAt: string;
updatedAt: string;
}
export class AutomationStore extends EventEmitter<AutomationStoreEvents> {
/** Per-schedule promise chain for serializing writes. */
private scheduleLocks: Map<string, Promise<void>> = new Map();
@@ -49,7 +70,7 @@ export class AutomationStore extends EventEmitter<AutomationStoreEvents> {
// ── Row Conversion ─────────────────────────────────────────────────
private rowToSchedule(row: any): ScheduledTask {
private rowToSchedule(row: ScheduleRow): ScheduledTask {
return {
id: row.id,
name: row.name,
@@ -126,7 +147,7 @@ export class AutomationStore extends EventEmitter<AutomationStoreEvents> {
// ── Persistence ────────────────────────────────────────────────────
private async readScheduleJson(id: string): Promise<ScheduledTask> {
const row = this.db.prepare('SELECT * FROM automations WHERE id = ?').get(id);
const row = this.db.prepare('SELECT * FROM automations WHERE id = ?').get(id) as unknown as ScheduleRow | undefined;
if (!row) {
throw Object.assign(new Error(`Schedule '${id}' not found`), { code: "ENOENT" });
}
@@ -223,7 +244,7 @@ export class AutomationStore extends EventEmitter<AutomationStoreEvents> {
}
async listSchedules(): Promise<ScheduledTask[]> {
const rows = this.db.prepare('SELECT * FROM automations ORDER BY createdAt ASC').all() as any[];
const rows = this.db.prepare('SELECT * FROM automations ORDER BY createdAt ASC').all() as unknown as ScheduleRow[];
return rows.map((row) => this.rowToSchedule(row));
}
@@ -372,7 +393,7 @@ export class AutomationStore extends EventEmitter<AutomationStoreEvents> {
const now = new Date().toISOString();
const rows = this.db.prepare(
'SELECT * FROM automations WHERE enabled = 1 AND nextRunAt IS NOT NULL AND nextRunAt <= ? AND scope = ?'
).all(now, scope) as any[];
).all(now, scope) as unknown as ScheduleRow[];
return rows.map((row) => this.rowToSchedule(row));
}
@@ -384,7 +405,7 @@ export class AutomationStore extends EventEmitter<AutomationStoreEvents> {
const now = new Date().toISOString();
const rows = this.db.prepare(
'SELECT * FROM automations WHERE enabled = 1 AND nextRunAt IS NOT NULL AND nextRunAt <= ?'
).all(now) as any[];
).all(now) as unknown as ScheduleRow[];
return rows.map((row) => this.rowToSchedule(row));
}
}

View File

@@ -40,6 +40,32 @@ export interface ChatStoreEvents {
"chat:message:deleted": [messageId: string];
}
// ── Row Interfaces ───────────────────────────────────────────────────
/** Database row shape for chat_sessions. */
interface ChatSessionRow {
id: string;
agentId: string;
title: string | null;
status: string;
projectId: string | null;
modelProvider: string | null;
modelId: string | null;
createdAt: string;
updatedAt: string;
}
/** Database row shape for chat_messages. */
interface ChatMessageRow {
id: string;
sessionId: string;
role: string;
content: string;
thinkingOutput: string | null;
metadata: string | null;
createdAt: string;
}
// ── ChatStore Class ─────────────────────────────────────────────────
export class ChatStore extends EventEmitter<ChatStoreEvents> {
@@ -56,7 +82,7 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
/**
* Convert a database row to a ChatSession object.
*/
private rowToSession(row: any): ChatSession {
private rowToSession(row: ChatSessionRow): ChatSession {
return {
id: row.id,
agentId: row.agentId,
@@ -73,7 +99,7 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
/**
* Convert a database row to a ChatMessage object.
*/
private rowToMessage(row: any): ChatMessage {
private rowToMessage(row: ChatMessageRow): ChatMessage {
return {
id: row.id,
sessionId: row.sessionId,
@@ -136,7 +162,7 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
* @returns The session, or undefined if not found
*/
getSession(id: string): ChatSession | undefined {
const row = this.db.prepare("SELECT * FROM chat_sessions WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM chat_sessions WHERE id = ?").get(id) as unknown as ChatSessionRow | undefined;
if (!row) return undefined;
return this.rowToSession(row);
}
@@ -174,7 +200,7 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
SELECT * FROM chat_sessions ${whereSql} ORDER BY updatedAt DESC
`).all(...params);
return (rows as any[]).map((row) => this.rowToSession(row));
return (rows as unknown as ChatSessionRow[]).map((row) => this.rowToSession(row));
}
/**
@@ -327,7 +353,7 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
LIMIT ? OFFSET ?
`).all(...params, limit, offset);
return (rows as any[]).map((row) => this.rowToMessage(row));
return (rows as unknown as ChatMessageRow[]).map((row) => this.rowToMessage(row));
}
/**
@@ -337,7 +363,7 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
* @returns The message, or undefined if not found
*/
getMessage(id: string): ChatMessage | undefined {
const row = this.db.prepare("SELECT * FROM chat_messages WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM chat_messages WHERE id = ?").get(id) as unknown as ChatMessageRow | undefined;
if (!row) return undefined;
return this.rowToMessage(row);
}
@@ -370,7 +396,7 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
`).all(...sessionIds);
const result = new Map<string, ChatMessage>();
for (const row of rows as any[]) {
for (const row of rows as unknown as ChatMessageRow[]) {
const message = this.rowToMessage(row);
result.set(message.sessionId, message);
}

View File

@@ -30,6 +30,23 @@ export interface MessageStoreEvents {
"message:deleted": [messageId: string];
}
// ── Row Interfaces ───────────────────────────────────────────────────
/** Database row shape for the messages table. */
interface MessageRow {
id: string;
fromId: string;
fromType: string;
toId: string;
toType: string;
content: string;
type: string;
read: number;
metadata: string | null;
createdAt: string;
updatedAt: string;
}
// ── Options Types ────────────────────────────────────────────────────
/** Options for MessageStore constructor */
@@ -95,7 +112,7 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
/**
* Convert a database row to a Message object.
*/
private rowToMessage(row: any): Message {
private rowToMessage(row: MessageRow): Message {
return {
id: row.id,
fromId: row.fromId,
@@ -172,7 +189,7 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
* @returns The message, or null if not found
*/
getMessage(id: string): Message | null {
const row = this.stmtGetById.get(id);
const row = this.stmtGetById.get(id) as unknown as MessageRow | undefined;
if (!row) return null;
return this.rowToMessage(row);
}
@@ -239,7 +256,7 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
LIMIT ? OFFSET ?
`).all(...params, limit, offset);
return (rows as any[]).map((row) => this.rowToMessage(row));
return (rows as unknown as MessageRow[]).map((row) => this.rowToMessage(row));
}
/**
@@ -336,7 +353,7 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
participantA.id, participantA.type,
);
return (rows as any[]).map((row) => this.rowToMessage(row));
return (rows as unknown as MessageRow[]).map((row) => this.rowToMessage(row));
}
/**
@@ -352,7 +369,7 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
const unreadRow = this.stmtCountUnread.get(ownerId, ownerType) as { count: number } | undefined;
const unreadCount = unreadRow?.count ?? 0;
const lastRow = this.stmtGetLastMessage.get(ownerId, ownerType);
const lastRow = this.stmtGetLastMessage.get(ownerId, ownerType) as unknown as MessageRow | undefined;
const lastMessage = lastRow ? this.rowToMessage(lastRow) : undefined;
return {

View File

@@ -127,6 +127,147 @@ export interface MissionStoreEvents {
"fix-feature:created": [FixFeatureCreatedPayload];
}
// ── Row Interfaces ──────────────────────────────────────────────────
/** Database row shape for the missions table. */
interface MissionRow {
id: string;
title: string;
description: string | null;
status: string;
interviewState: string;
autoAdvance: number;
autopilotEnabled: number;
autopilotState: string;
lastAutopilotActivityAt: string | null;
createdAt: string;
updatedAt: string;
}
/** Database row shape for the milestones table. */
interface MilestoneRow {
id: string;
missionId: string;
title: string;
description: string | null;
status: string;
orderIndex: number;
interviewState: string;
dependencies: string | null;
planningNotes: string | null;
verification: string | null;
validationState: string | null;
createdAt: string;
updatedAt: string;
}
/** Database row shape for the mission_contract_assertions table. */
interface AssertionRow {
id: string;
milestoneId: string;
title: string;
assertion: string;
status: string;
orderIndex: number;
createdAt: string;
updatedAt: string;
}
/** Database row shape for the mission_feature_assertions table. */
interface FeatureAssertionLinkRow {
featureId: string;
assertionId: string;
createdAt: string;
}
/** Database row shape for the slices table. */
interface SliceRow {
id: string;
milestoneId: string;
title: string;
description: string | null;
status: string;
orderIndex: number;
activatedAt: string | null;
planState: string | null;
planningNotes: string | null;
verification: string | null;
createdAt: string;
updatedAt: string;
}
/** Database row shape for the mission_features table. */
interface FeatureRow {
id: string;
sliceId: string;
taskId: string | null;
title: string;
description: string | null;
acceptanceCriteria: string | null;
status: string;
createdAt: string;
updatedAt: string;
loopState: string | null;
implementationAttemptCount: number | null;
validatorAttemptCount: number | null;
lastValidatorRunId: string | null;
lastValidatorStatus: string | null;
generatedFromFeatureId: string | null;
generatedFromRunId: string | null;
}
/** Database row shape for the mission_events table. */
interface MissionEventRow {
id: string;
missionId: string;
eventType: string;
description: string;
metadata: string | null;
timestamp: string;
seq: number | null;
}
/** Database row shape for the mission_validator_runs table. */
interface ValidatorRunRow {
id: string;
featureId: string;
milestoneId: string;
sliceId: string;
status: string;
triggerType: string | null;
implementationAttempt: number | null;
validatorAttempt: number | null;
taskId: string | null;
summary: string | null;
blockedReason: string | null;
startedAt: string;
completedAt: string | null;
createdAt: string;
updatedAt: string;
}
/** Database row shape for the mission_validator_failures table. */
interface FailureRow {
id: string;
runId: string;
featureId: string;
assertionId: string;
message: string | null;
expected: string | null;
actual: string | null;
createdAt: string;
}
/** Database row shape for the mission_fix_feature_lineage table. */
interface LineageRow {
id: string;
sourceFeatureId: string;
fixFeatureId: string;
runId: string;
failedAssertionIds: string | null;
createdAt: string;
}
// ── MissionStore Class ──────────────────────────────────────────────
export class MissionStore extends EventEmitter<MissionStoreEvents> {
@@ -158,7 +299,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a Mission object.
*/
private rowToMission(row: any): Mission {
private rowToMission(row: MissionRow): Mission {
return {
id: row.id,
title: row.title,
@@ -177,7 +318,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a Milestone object.
*/
private rowToMilestone(row: any): Milestone {
private rowToMilestone(row: MilestoneRow): Milestone {
return {
id: row.id,
missionId: row.missionId,
@@ -198,7 +339,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a MissionContractAssertion object.
*/
private rowToAssertion(row: any): MissionContractAssertion {
private rowToAssertion(row: AssertionRow): MissionContractAssertion {
return {
id: row.id,
milestoneId: row.milestoneId,
@@ -214,7 +355,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a FeatureAssertionLink object.
*/
private rowToFeatureAssertionLink(row: any): FeatureAssertionLink {
private rowToFeatureAssertionLink(row: FeatureAssertionLinkRow): FeatureAssertionLink {
return {
featureId: row.featureId,
assertionId: row.assertionId,
@@ -225,7 +366,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a Slice object.
*/
private rowToSlice(row: any): Slice {
private rowToSlice(row: SliceRow): Slice {
return {
id: row.id,
milestoneId: row.milestoneId,
@@ -245,7 +386,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a MissionFeature object.
*/
private rowToFeature(row: any): MissionFeature {
private rowToFeature(row: FeatureRow): MissionFeature {
return {
id: row.id,
sliceId: row.sliceId,
@@ -269,7 +410,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a MissionEvent object.
*/
private rowToMissionEvent(row: any): MissionEvent {
private rowToMissionEvent(row: MissionEventRow): MissionEvent {
return {
id: row.id,
missionId: row.missionId,
@@ -284,7 +425,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a MissionValidatorRun object.
*/
private rowToValidatorRun(row: any): MissionValidatorRun {
private rowToValidatorRun(row: ValidatorRunRow): MissionValidatorRun {
return {
id: row.id,
featureId: row.featureId,
@@ -307,7 +448,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a MissionAssertionFailureRecord object.
*/
private rowToFailure(row: any): MissionAssertionFailureRecord {
private rowToFailure(row: FailureRow): MissionAssertionFailureRecord {
return {
id: row.id,
runId: row.runId,
@@ -323,7 +464,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
/**
* Convert a database row to a MissionFixFeatureLineage object.
*/
private rowToLineage(row: any): MissionFixFeatureLineage {
private rowToLineage(row: LineageRow): MissionFixFeatureLineage {
return {
id: row.id,
sourceFeatureId: row.sourceFeatureId,
@@ -388,7 +529,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
* @returns The mission, or undefined if not found
*/
getMission(id: string): Mission | undefined {
const row = this.db.prepare("SELECT * FROM missions WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM missions WHERE id = ?").get(id) as unknown as MissionRow | undefined;
if (!row) return undefined;
return this.rowToMission(row);
}
@@ -429,7 +570,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
*/
listMissions(): Mission[] {
const rows = this.db.prepare("SELECT * FROM missions ORDER BY createdAt DESC").all();
return (rows as any[]).map((row) => this.rowToMission(row));
return (rows as unknown as MissionRow[]).map((row) => this.rowToMission(row));
}
/**
@@ -494,19 +635,19 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
// 2. Batch query all milestones
const milestoneRows = this.db.prepare(
"SELECT * FROM milestones ORDER BY orderIndex ASC"
).all() as any[];
).all() as unknown as MilestoneRow[];
const allMilestones = milestoneRows.map((row) => this.rowToMilestone(row));
// 3. Batch query all slices
const sliceRows = this.db.prepare(
"SELECT * FROM slices ORDER BY orderIndex ASC"
).all() as any[];
).all() as unknown as SliceRow[];
const allSlices = sliceRows.map((row) => this.rowToSlice(row));
// 4. Batch query all features
const featureRows = this.db.prepare(
"SELECT * FROM mission_features ORDER BY createdAt ASC"
).all() as any[];
).all() as unknown as FeatureRow[];
const allFeatures = featureRows.map((row) => this.rowToFeature(row));
// 5. Group in-memory: slices by milestoneId, features by sliceId
@@ -585,19 +726,19 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
// 1. Batch query all milestones
const milestoneRows = this.db.prepare(
"SELECT * FROM milestones ORDER BY orderIndex ASC"
).all() as any[];
).all() as unknown as MilestoneRow[];
const allMilestones = milestoneRows.map((row) => this.rowToMilestone(row));
// 2. Batch query all slices
const sliceRows = this.db.prepare(
"SELECT * FROM slices ORDER BY orderIndex ASC"
).all() as any[];
).all() as unknown as SliceRow[];
const allSlices = sliceRows.map((row) => this.rowToSlice(row));
// 3. Batch query all features
const featureRows = this.db.prepare(
"SELECT * FROM mission_features ORDER BY createdAt ASC"
).all() as any[];
).all() as unknown as FeatureRow[];
const allFeatures = featureRows.map((row) => this.rowToFeature(row));
// 4. Batch query all failed task IDs
@@ -798,7 +939,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
WHERE ${whereSql}
ORDER BY timestamp DESC, id DESC
LIMIT ? OFFSET ?
`).all(...params, limit, offset) as any[];
`).all(...params, limit, offset) as unknown as MissionEventRow[];
return {
events: rows.map((row) => this.rowToMissionEvent(row)),
@@ -1048,7 +1189,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
* @returns The milestone, or undefined if not found
*/
getMilestone(id: string): Milestone | undefined {
const row = this.db.prepare("SELECT * FROM milestones WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM milestones WHERE id = ?").get(id) as unknown as MilestoneRow | undefined;
if (!row) return undefined;
return this.rowToMilestone(row);
}
@@ -1063,7 +1204,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM milestones WHERE missionId = ? ORDER BY orderIndex ASC"
).all(missionId);
return (rows as any[]).map((row) => this.rowToMilestone(row));
return (rows as unknown as MilestoneRow[]).map((row) => this.rowToMilestone(row));
}
/**
@@ -1260,7 +1401,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
* @returns The slice, or undefined if not found
*/
getSlice(id: string): Slice | undefined {
const row = this.db.prepare("SELECT * FROM slices WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM slices WHERE id = ?").get(id) as unknown as SliceRow | undefined;
if (!row) return undefined;
return this.rowToSlice(row);
}
@@ -1275,7 +1416,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM slices WHERE milestoneId = ? ORDER BY orderIndex ASC"
).all(milestoneId);
return (rows as any[]).map((row) => this.rowToSlice(row));
return (rows as unknown as SliceRow[]).map((row) => this.rowToSlice(row));
}
/**
@@ -1524,7 +1665,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
* @returns The feature, or undefined if not found
*/
getFeature(id: string): MissionFeature | undefined {
const row = this.db.prepare("SELECT * FROM mission_features WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM mission_features WHERE id = ?").get(id) as unknown as FeatureRow | undefined;
if (!row) return undefined;
return this.rowToFeature(row);
}
@@ -1539,7 +1680,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM mission_features WHERE sliceId = ? ORDER BY createdAt ASC"
).all(sliceId);
return (rows as any[]).map((row) => this.rowToFeature(row));
return (rows as unknown as FeatureRow[]).map((row) => this.rowToFeature(row));
}
/**
@@ -1780,7 +1921,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
* @returns The feature, or undefined if no feature is linked to this task
*/
getFeatureByTaskId(taskId: string): MissionFeature | undefined {
const row = this.db.prepare("SELECT * FROM mission_features WHERE taskId = ?").get(taskId);
const row = this.db.prepare("SELECT * FROM mission_features WHERE taskId = ?").get(taskId) as unknown as FeatureRow | undefined;
if (!row) return undefined;
return this.rowToFeature(row);
}
@@ -1976,7 +2117,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
* @returns The validator run, or undefined if not found
*/
getValidatorRun(id: string): MissionValidatorRun | undefined {
const row = this.db.prepare("SELECT * FROM mission_validator_runs WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM mission_validator_runs WHERE id = ?").get(id) as ValidatorRunRow | undefined;
if (!row) return undefined;
return this.rowToValidatorRun(row);
}
@@ -2057,7 +2198,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM mission_validator_failures WHERE runId = ? ORDER BY createdAt ASC"
).all(runId);
return (rows as any[]).map((row) => this.rowToFailure(row));
return (rows as unknown as FailureRow[]).map((row) => this.rowToFailure(row));
}
/**
@@ -2070,7 +2211,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM mission_validator_runs WHERE featureId = ? ORDER BY startedAt DESC"
).all(featureId);
return (rows as any[]).map((row) => this.rowToValidatorRun(row));
return (rows as unknown as ValidatorRunRow[]).map((row) => this.rowToValidatorRun(row));
}
/**
@@ -2230,10 +2371,10 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
// Get lineage entries where this feature is the source or the fix
const sourceLineageRows = this.db.prepare(
"SELECT * FROM mission_fix_feature_lineage WHERE sourceFeatureId = ?"
).all(featureId) as any[];
).all(featureId) as unknown as LineageRow[];
const fixLineageRows = this.db.prepare(
"SELECT * FROM mission_fix_feature_lineage WHERE fixFeatureId = ?"
).all(featureId) as any[];
).all(featureId) as unknown as LineageRow[];
const lineage = [
...sourceLineageRows.map((row) => this.rowToLineage(row)),
@@ -2411,7 +2552,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
* @returns The assertion, or undefined if not found
*/
getContractAssertion(id: string): MissionContractAssertion | undefined {
const row = this.db.prepare("SELECT * FROM mission_contract_assertions WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM mission_contract_assertions WHERE id = ?").get(id) as unknown as AssertionRow | undefined;
if (!row) return undefined;
return this.rowToAssertion(row);
}
@@ -2429,7 +2570,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM mission_contract_assertions WHERE milestoneId = ? ORDER BY orderIndex ASC, createdAt ASC, id ASC"
).all(milestoneId);
return (rows as any[]).map((row) => this.rowToAssertion(row));
return (rows as unknown as AssertionRow[]).map((row) => this.rowToAssertion(row));
}
/**
@@ -2626,7 +2767,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
WHERE fa.featureId = ?
ORDER BY ca.orderIndex ASC, ca.createdAt ASC, ca.id ASC
`).all(featureId);
return (rows as any[]).map((row) => this.rowToAssertion(row));
return (rows as unknown as AssertionRow[]).map((row) => this.rowToAssertion(row));
}
/**
@@ -2642,7 +2783,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
WHERE fa.assertionId = ?
ORDER BY mf.createdAt ASC
`).all(assertionId);
return (rows as any[]).map((row) => this.rowToFeature(row));
return (rows as unknown as FeatureRow[]).map((row) => this.rowToFeature(row));
}
// ── Validation Rollup Operations ───────────────────────────────────

View File

@@ -42,6 +42,25 @@ export interface PluginUpdateInput {
dependencies?: string[];
}
/** Database row shape for the plugins table. */
interface PluginRow {
id: string;
name: string;
version: string;
description: string | null;
author: string | null;
homepage: string | null;
path: string;
enabled: number;
state: string;
settings: string | null;
settingsSchema: string | null;
error: string | null;
dependencies: string | null;
createdAt: string;
updatedAt: string;
}
export class PluginStore extends EventEmitter<PluginStoreEvents> {
/** SQLite database instance */
private _db: Database | null = null;
@@ -70,7 +89,7 @@ export class PluginStore extends EventEmitter<PluginStoreEvents> {
// ── Row Conversion ─────────────────────────────────────────────────
private rowToPlugin(row: any): PluginInstallation {
private rowToPlugin(row: PluginRow): PluginInstallation {
return {
id: row.id,
name: row.name,
@@ -263,7 +282,7 @@ export class PluginStore extends EventEmitter<PluginStoreEvents> {
* Get a plugin by id.
*/
async getPlugin(id: string): Promise<PluginInstallation> {
const row = this.db.prepare("SELECT * FROM plugins WHERE id = ?").get(id) as any;
const row = this.db.prepare("SELECT * FROM plugins WHERE id = ?").get(id) as unknown as PluginRow | undefined;
if (!row) {
throw Object.assign(new Error(`Plugin "${id}" not found`), { code: "ENOENT" });
}
@@ -278,7 +297,7 @@ export class PluginStore extends EventEmitter<PluginStoreEvents> {
): Promise<PluginInstallation[]> {
let sql = "SELECT * FROM plugins";
const conditions: string[] = [];
const params: any[] = [];
const params: (string | number)[] = [];
if (filter?.enabled !== undefined) {
conditions.push("enabled = ?");
@@ -294,7 +313,7 @@ export class PluginStore extends EventEmitter<PluginStoreEvents> {
}
sql += " ORDER BY createdAt ASC";
const rows = this.db.prepare(sql).all(...params) as any[];
const rows = this.db.prepare(sql).all(...params) as unknown as PluginRow[];
return rows.map((row) => this.rowToPlugin(row));
}
@@ -424,7 +443,7 @@ export class PluginStore extends EventEmitter<PluginStoreEvents> {
const now = new Date().toISOString();
const setClauses: string[] = ["updatedAt = ?"];
const params: any[] = [now];
const params: (string | null)[] = [now];
if (updates.name !== undefined) {
setClauses.push("name = ?");

View File

@@ -68,6 +68,39 @@ export interface RoadmapStoreEvents {
"feature:moved": [{ feature: RoadmapFeature; fromMilestoneId: string; toMilestoneId: string }];
}
// ── Row Interfaces ──────────────────────────────────────────────────
/** Database row shape for roadmaps. */
interface RoadmapRow {
id: string;
title: string;
description: string | null;
createdAt: string;
updatedAt: string;
}
/** Database row shape for roadmap_milestones. */
interface RoadmapMilestoneRow {
id: string;
roadmapId: string;
title: string;
description: string | null;
orderIndex: number;
createdAt: string;
updatedAt: string;
}
/** Database row shape for roadmap_features. */
interface RoadmapFeatureRow {
id: string;
milestoneId: string;
title: string;
description: string | null;
orderIndex: number;
createdAt: string;
updatedAt: string;
}
// ── RoadmapStore Class ──────────────────────────────────────────────
export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
@@ -103,7 +136,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
// ── Row-to-Object Converters ───────────────────────────────────────
private rowToRoadmap(row: any): Roadmap {
private rowToRoadmap(row: RoadmapRow): Roadmap {
return {
id: row.id,
title: row.title,
@@ -113,7 +146,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
};
}
private rowToMilestone(row: any): RoadmapMilestone {
private rowToMilestone(row: RoadmapMilestoneRow): RoadmapMilestone {
return {
id: row.id,
roadmapId: row.roadmapId,
@@ -125,7 +158,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
};
}
private rowToFeature(row: any): RoadmapFeature {
private rowToFeature(row: RoadmapFeatureRow): RoadmapFeature {
return {
id: row.id,
milestoneId: row.milestoneId,
@@ -180,7 +213,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
* @returns The roadmap, or undefined if not found
*/
getRoadmap(id: string): Roadmap | undefined {
const row = this.db.prepare("SELECT * FROM roadmaps WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM roadmaps WHERE id = ?").get(id) as unknown as RoadmapRow | undefined;
if (!row) return undefined;
return this.rowToRoadmap(row);
}
@@ -194,7 +227,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM roadmaps ORDER BY createdAt DESC"
).all();
return (rows as any[]).map((row) => this.rowToRoadmap(row));
return (rows as unknown as RoadmapRow[]).map((row) => this.rowToRoadmap(row));
}
/**
@@ -317,7 +350,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
* @returns The milestone, or undefined if not found
*/
getMilestone(id: string): RoadmapMilestone | undefined {
const row = this.db.prepare("SELECT * FROM roadmap_milestones WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM roadmap_milestones WHERE id = ?").get(id) as unknown as RoadmapMilestoneRow | undefined;
if (!row) return undefined;
return this.rowToMilestone(row);
}
@@ -335,7 +368,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM roadmap_milestones WHERE roadmapId = ? ORDER BY orderIndex ASC, createdAt ASC, id ASC"
).all(roadmapId);
return (rows as any[]).map((row) => this.rowToMilestone(row));
return (rows as unknown as RoadmapMilestoneRow[]).map((row) => this.rowToMilestone(row));
}
/**
@@ -459,7 +492,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
* @returns The feature, or undefined if not found
*/
getFeature(id: string): RoadmapFeature | undefined {
const row = this.db.prepare("SELECT * FROM roadmap_features WHERE id = ?").get(id);
const row = this.db.prepare("SELECT * FROM roadmap_features WHERE id = ?").get(id) as unknown as RoadmapFeatureRow | undefined;
if (!row) return undefined;
return this.rowToFeature(row);
}
@@ -477,7 +510,7 @@ export class RoadmapStore extends EventEmitter<RoadmapStoreEvents> {
const rows = this.db.prepare(
"SELECT * FROM roadmap_features WHERE milestoneId = ? ORDER BY orderIndex ASC, createdAt ASC, id ASC"
).all(milestoneId);
return (rows as any[]).map((row) => this.rowToFeature(row));
return (rows as unknown as RoadmapFeatureRow[]).map((row) => this.rowToFeature(row));
}
/**

View File

@@ -33,6 +33,31 @@ export interface RoutineStoreEvents {
"routine:run": [data: { routine: Routine; result: RoutineExecutionResult }];
}
/** Database row shape for the routines table. */
interface RoutineRow {
id: string;
agentId: string;
name: string;
description: string | null;
triggerType: string;
triggerConfig: string | null;
command: string | null;
steps: string | null;
timeoutMs: number | null;
catchUpPolicy: string;
executionPolicy: string;
enabled: number;
lastRunAt: string | null;
lastRunResult: string | null;
nextRunAt: string | null;
runCount: number;
runHistory: string;
catchUpLimit: number;
scope: string;
createdAt: string;
updatedAt: string;
}
export class RoutineStore extends EventEmitter<RoutineStoreEvents> {
/** SQLite database instance (lazy init). */
private _db: Database | null = null;
@@ -66,7 +91,7 @@ export class RoutineStore extends EventEmitter<RoutineStoreEvents> {
// ── Row Conversion ─────────────────────────────────────────────────
private rowToRoutine(row: any): Routine {
private rowToRoutine(row: RoutineRow): Routine {
const triggerConfig = fromJson<{
cronExpression?: string;
timezone?: string;
@@ -288,7 +313,7 @@ export class RoutineStore extends EventEmitter<RoutineStoreEvents> {
* Get a routine by ID.
*/
async getRoutine(id: string): Promise<Routine> {
const row = this.db.prepare("SELECT * FROM routines WHERE id = ?").get(id) as any;
const row = this.db.prepare("SELECT * FROM routines WHERE id = ?").get(id) as unknown as RoutineRow | undefined;
if (!row) {
throw Object.assign(new Error(`Routine '${id}' not found`), { code: "ENOENT" });
}
@@ -299,7 +324,7 @@ export class RoutineStore extends EventEmitter<RoutineStoreEvents> {
* List all routines.
*/
async listRoutines(): Promise<Routine[]> {
const rows = this.db.prepare("SELECT * FROM routines ORDER BY createdAt ASC").all() as any[];
const rows = this.db.prepare("SELECT * FROM routines ORDER BY createdAt ASC").all() as unknown as RoutineRow[];
return rows.map((row) => this.rowToRoutine(row));
}
@@ -480,7 +505,7 @@ export class RoutineStore extends EventEmitter<RoutineStoreEvents> {
const now = new Date().toISOString();
const rows = this.db.prepare(
"SELECT * FROM routines WHERE enabled = 1 AND nextRunAt IS NOT NULL AND nextRunAt <= ? AND scope = ?"
).all(now, scope) as any[];
).all(now, scope) as unknown as RoutineRow[];
return rows.map((row) => this.rowToRoutine(row));
}
@@ -492,7 +517,7 @@ export class RoutineStore extends EventEmitter<RoutineStoreEvents> {
const now = new Date().toISOString();
const rows = this.db.prepare(
"SELECT * FROM routines WHERE enabled = 1 AND nextRunAt IS NOT NULL AND nextRunAt <= ?"
).all(now) as any[];
).all(now) as unknown as RoutineRow[];
return rows.map((row) => this.rowToRoutine(row));
}
}

View File

@@ -20,6 +20,120 @@ import { ensureMemoryFileWithBackend } from "./project-memory.js";
import { runCommandAsync } from "./run-command.js";
import { createLogger } from "./logger.js";
/** Database row shape for the tasks table (all columns). */
interface TaskRow {
id: string;
title: string | null;
description: string;
column: string;
status: string | null;
size: string | null;
reviewLevel: number | null;
currentStep: number;
worktree: string | null;
blockedBy: string | null;
paused: number | null;
baseBranch: string | null;
branch: string | null;
baseCommitSha: string | null;
modelPresetId: string | null;
modelProvider: string | null;
modelId: string | null;
validatorModelProvider: string | null;
validatorModelId: string | null;
planningModelProvider: string | null;
planningModelId: string | null;
mergeRetries: number | null;
workflowStepRetries: number | null;
stuckKillCount: number | null;
postReviewFixCount: number | null;
recoveryRetryCount: number | null;
taskDoneRetryCount: number | null;
nextRecoveryAt: string | null;
error: string | null;
summary: string | null;
thinkingLevel: string | null;
executionMode: string | null;
createdAt: string;
updatedAt: string;
columnMovedAt: string | null;
dependencies: string | null;
steps: string | null;
log: string | null;
attachments: string | null;
steeringComments: string | null;
comments: string | null;
workflowStepResults: string | null;
prInfo: string | null;
issueInfo: string | null;
mergeDetails: string | null;
breakIntoSubtasks: number | null;
enabledWorkflowSteps: string | null;
modifiedFiles: string | null;
missionId: string | null;
sliceId: string | null;
assignedAgentId: string | null;
assigneeUserId: string | null;
checkedOutBy: string | null;
checkedOutAt: string | null;
}
/** Database row shape for the task_documents table. */
interface TaskDocumentRow {
id: string;
taskId: string;
key: string;
content: string;
revision: number;
author: string;
metadata: string | null;
createdAt: string;
updatedAt: string;
}
/** Database row shape for the task_document_revisions table. */
interface TaskDocumentRevisionRow {
id: number;
taskId: string;
key: string;
content: string;
revision: number;
author: string;
metadata: string | null;
createdAt: string;
}
/** Database row shape for the runAuditEvents table. */
interface RunAuditEventRow {
id: string;
timestamp: string;
taskId: string | null;
agentId: string;
runId: string;
domain: string;
mutationType: string;
target: string;
metadata: string | null;
}
/** Database row shape for the config table. */
interface ConfigRow {
nextId: number;
settings: string | null;
nextWorkflowStepId: number | null;
}
/** Database row shape for the activityLog table. */
interface ActivityLogRow {
id: string;
timestamp: string;
type: string;
taskId: string | null;
taskTitle: string | null;
details: string;
metadata: string | null;
}
const TASK_ACTIVITY_LOG_ENTRY_LIMIT = 1_000;
const TASK_ACTIVITY_LOG_OUTCOME_LIMIT = 4_000;
const ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT = 25;
@@ -313,14 +427,14 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
/**
* Convert a database row to a Task object, parsing JSON columns.
*/
private rowToTask(row: any): Task {
private rowToTask(row: TaskRow): Task {
return {
id: row.id,
title: row.title || undefined,
description: row.description,
column: row.column as Column,
status: row.status || undefined,
size: row.size || undefined,
size: (row.size || undefined) as Task["size"],
reviewLevel: row.reviewLevel ?? undefined,
currentStep: row.currentStep || 0,
worktree: row.worktree || undefined,
@@ -345,8 +459,8 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
nextRecoveryAt: row.nextRecoveryAt || undefined,
error: row.error || undefined,
summary: row.summary || undefined,
thinkingLevel: row.thinkingLevel || undefined,
executionMode: row.executionMode || undefined,
thinkingLevel: (row.thinkingLevel || undefined) as Task["thinkingLevel"],
executionMode: (row.executionMode || undefined) as Task["executionMode"],
createdAt: row.createdAt,
updatedAt: row.updatedAt,
columnMovedAt: row.columnMovedAt || undefined,
@@ -550,7 +664,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
/**
* Convert a task_documents row to a TaskDocument object.
*/
private rowToTaskDocument(row: any): TaskDocument {
private rowToTaskDocument(row: TaskDocumentRow): TaskDocument {
return {
id: row.id,
taskId: row.taskId,
@@ -567,7 +681,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
/**
* Convert a task_document_revisions row to a TaskDocumentRevision object.
*/
private rowToTaskDocumentRevision(row: any): TaskDocumentRevision {
private rowToTaskDocumentRevision(row: TaskDocumentRevisionRow): TaskDocumentRevision {
return {
id: row.id,
taskId: row.taskId,
@@ -780,7 +894,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
const selectClause = options?.activityLogLimit
? this.getTaskSelectClauseWithActivityLogLimit(options.activityLogLimit)
: "*";
const row = this.db.prepare(`SELECT ${selectClause} FROM tasks WHERE id = ?`).get(id);
const row = this.db.prepare(`SELECT ${selectClause} FROM tasks WHERE id = ?`).get(id) as unknown as TaskRow | undefined;
if (!row) return undefined;
return this.rowToTask(row);
}
@@ -1129,7 +1243,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
if (config.settings) {
for (const key of Object.keys(config.settings)) {
if (!isGlobalSettingsKey(key)) {
(projectSettings as any)[key] = (config.settings as any)[key];
(projectSettings as Record<string, unknown>)[key] = (config.settings as Record<string, unknown>)[key];
}
}
}
@@ -1164,7 +1278,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
if (projectSettings) {
for (const key of Object.keys(projectSettings)) {
if (!isGlobalSettingsKey(key)) {
(projectScoped as any)[key] = (projectSettings as any)[key];
(projectScoped as Record<string, unknown>)[key] = (projectSettings as Record<string, unknown>)[key];
}
}
}
@@ -1320,7 +1434,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
}
private async readConfig(): Promise<BoardConfig> {
const row = this.db.prepare("SELECT * FROM config WHERE id = 1").get() as any;
const row = this.db.prepare("SELECT * FROM config WHERE id = 1").get() as unknown as ConfigRow | undefined;
if (!row) {
return { nextId: 1 };
}
@@ -1353,7 +1467,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
* Returns only the core config fields needed for config.json serialization.
*/
private readConfigFast(): BoardConfig {
const row = this.db.prepare("SELECT * FROM config WHERE id = 1").get() as any;
const row = this.db.prepare("SELECT * FROM config WHERE id = 1").get() as ConfigRow | undefined;
if (!row) {
return { nextId: 1 };
}
@@ -1404,10 +1518,10 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
// Use withConfigLock to ensure the entire ID allocation + config sync is serialized
return this.withConfigLock(async () => {
const id = this.db.transaction(() => {
const row = this.db.prepare("SELECT nextId, settings FROM config WHERE id = 1").get() as any;
const settings = fromJson<Settings>(row.settings);
const row = this.db.prepare("SELECT nextId, settings FROM config WHERE id = 1").get() as unknown as { nextId: number; settings: string | null } | undefined;
const settings = fromJson<Settings>(row?.settings ?? null);
const prefix = settings?.taskPrefix || "KB";
const nextId = row.nextId || 1;
const nextId = row?.nextId || 1;
const taskId = `${prefix}-${String(nextId).padStart(3, "0")}`;
this.db.prepare("UPDATE config SET nextId = ? WHERE id = 1").run(nextId + 1);
this.db.bumpLastModified();
@@ -1941,7 +2055,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
const sql = `SELECT ${selectClause} FROM tasks${whereClause} ORDER BY createdAt ASC`;
const rows = this.db.prepare(sql).all(...params);
const activeTasks = await Promise.all((rows as any[]).map(async (row) => {
const activeTasks = await Promise.all((rows as unknown as TaskRow[]).map(async (row) => {
const task = this.rowToTask(row);
if (!slim || task.steps.length > 0) {
return task;
@@ -2025,7 +2139,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
const slim = options?.slim ?? false;
const selectClause = this.getTaskSelectClause(slim, "t");
let rows: any[];
let rows: TaskRow[];
if (this.db.fts5Available) {
// For FTS5 MATCH, quote tokens that contain special characters like hyphens
// to prevent them from being interpreted as operators
@@ -2045,7 +2159,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
${whereClause}
ORDER BY rank
LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
`).all(ftsQuery) as any[];
`).all(ftsQuery) as unknown as TaskRow[];
} else {
// LIKE fallback: any token matching any searchable column counts as a hit.
// Tokens are OR'd; per token we OR across id/title/description/comments.
@@ -2066,7 +2180,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
WHERE (${whereTokens})${archivedClause}
ORDER BY t.createdAt ASC
LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
`).all(...params) as any[];
`).all(...params) as unknown as TaskRow[];
}
const activeMatches = await Promise.all(rows.map(async (row) => {
@@ -2093,7 +2207,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
}
const tasksById = new Map(tasks.map((task) => [task.id, task]));
const isCheckoutAware = "checkoutTask" in this && typeof (this as any).checkoutTask === "function";
const isCheckoutAware = "checkoutTask" in this && typeof (this as Record<string, unknown>).checkoutTask === "function";
const isDoneLike = (task: Task | undefined) => task?.column === "done" || task?.column === "archived";
const sortByOldestColumnMove = (a: Task, b: Task) => {
const aSortAt = a.columnMovedAt ?? a.createdAt;
@@ -2468,7 +2582,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
domain: "database",
mutationType: "task:update",
target: task.id,
metadata: { updatedFields: Object.keys(updates).filter((k) => (updates as any)[k] !== undefined) },
metadata: { updatedFields: Object.keys(updates).filter((k) => (updates as Record<string, unknown>)[k] !== undefined) },
});
} else {
await this.atomicWriteTaskJson(dir, task);
@@ -2717,7 +2831,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
/**
* Convert a database row to a RunAuditEvent object.
*/
private rowToRunAuditEvent(row: any): RunAuditEvent {
private rowToRunAuditEvent(row: RunAuditEventRow): RunAuditEvent {
return {
id: row.id,
timestamp: row.timestamp,
@@ -2842,7 +2956,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
${whereClause}
${orderClause}
${limitClause}
`).all(...sqlParams) as any[];
`).all(...sqlParams) as unknown as RunAuditEventRow[];
return rows.map((row) => this.rowToRunAuditEvent(row));
}
@@ -3559,8 +3673,8 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
// Use lastKnownPollTime (ISO string) to filter — much cheaper than full scan.
const selectClause = this.getTaskSelectClause(true);
const changedRows = this.lastPollTime
? this.db.prepare(`SELECT ${selectClause} FROM tasks WHERE updatedAt > ? OR columnMovedAt > ?`).all(this.lastPollTime, this.lastPollTime) as any[]
: this.db.prepare(`SELECT ${selectClause} FROM tasks`).all() as any[];
? this.db.prepare(`SELECT ${selectClause} FROM tasks WHERE updatedAt > ? OR columnMovedAt > ?`).all(this.lastPollTime, this.lastPollTime) as unknown as TaskRow[]
: this.db.prepare(`SELECT ${selectClause} FROM tasks`).all() as unknown as TaskRow[];
this.lastPollTime = new Date().toISOString();
for (let i = 0; i < changedRows.length; i++) {
@@ -4015,7 +4129,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
async getTaskDocuments(taskId: string): Promise<TaskDocument[]> {
const rows = this.db
.prepare("SELECT * FROM task_documents WHERE taskId = ? ORDER BY key")
.all(taskId) as any[];
.all(taskId) as unknown as TaskDocumentRow[];
return rows.map((row) => this.rowToTaskDocument(row));
}
@@ -4036,7 +4150,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
FROM task_documents td
JOIN tasks t ON td.taskId = t.id
`;
const params: any[] = [];
const params: (string | number)[] = [];
if (options?.searchQuery && options.searchQuery.trim() !== "") {
const query = `%${options.searchQuery.trim()}%`;
@@ -4047,7 +4161,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
sql += ` ORDER BY td.updatedAt DESC LIMIT ? OFFSET ?`;
params.push(limit, offset);
const rows = this.db.prepare(sql).all(...params) as any[];
const rows = this.db.prepare(sql).all(...params) as unknown as (TaskDocumentRow & { taskTitle: string; taskDescription: string; taskColumn: string })[];
return rows.map((row) => {
const doc = this.rowToTaskDocument(row);
return {
@@ -4065,7 +4179,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
async getTaskDocument(taskId: string, key: string): Promise<TaskDocument | null> {
const row = this.db
.prepare("SELECT * FROM task_documents WHERE taskId = ? AND key = ?")
.get(taskId, key) as any | undefined;
.get(taskId, key) as unknown as TaskDocumentRow | undefined;
if (!row) return null;
return this.rowToTaskDocument(row);
}
@@ -4096,7 +4210,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
const document = this.db.transaction(() => {
const existing = this.db
.prepare("SELECT * FROM task_documents WHERE taskId = ? AND key = ?")
.get(taskId, input.key) as any | undefined;
.get(taskId, input.key) as TaskDocumentRow | undefined;
if (existing) {
this.db.prepare(
@@ -4144,7 +4258,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
const row = this.db
.prepare("SELECT * FROM task_documents WHERE taskId = ? AND key = ?")
.get(taskId, input.key) as any | undefined;
.get(taskId, input.key) as TaskDocumentRow | undefined;
if (!row) {
throw new Error(`Failed to upsert document ${input.key} for task ${taskId}`);
@@ -4174,12 +4288,12 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
.prepare(
"SELECT * FROM task_document_revisions WHERE taskId = ? AND key = ? ORDER BY revision DESC LIMIT ?",
)
.all(taskId, key, Math.max(0, options.limit ?? 0)) as any[])
.all(taskId, key, Math.max(0, options.limit ?? 0)) as unknown as TaskDocumentRevisionRow[])
: (this.db
.prepare(
"SELECT * FROM task_document_revisions WHERE taskId = ? AND key = ? ORDER BY revision DESC",
)
.all(taskId, key) as any[]);
.all(taskId, key) as unknown as TaskDocumentRevisionRow[]);
return rows.map((row) => this.rowToTaskDocumentRevision(row));
}
@@ -4539,7 +4653,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
}
private async migrateActiveArchivedTasksToArchiveDb(): Promise<void> {
const rows = this.db.prepare(`SELECT * FROM tasks WHERE "column" = 'archived'`).all() as any[];
const rows = this.db.prepare(`SELECT * FROM tasks WHERE "column" = 'archived'`).all() as unknown as TaskRow[];
if (rows.length === 0) {
return;
}
@@ -5183,7 +5297,7 @@ ${notificationsSection}`;
*/
private getSettingsSync(): Settings {
try {
const row = this.db.prepare("SELECT settings FROM config WHERE id = 1").get() as any;
const row = this.db.prepare("SELECT settings FROM config WHERE id = 1").get() as { settings: string | null } | undefined;
if (!row) return DEFAULT_SETTINGS;
const settings = fromJson<Settings>(row.settings);
return { ...DEFAULT_SETTINGS, ...settings };
@@ -5242,7 +5356,7 @@ ${notificationsSection}`;
*/
async getActivityLog(options?: { limit?: number; since?: string; type?: ActivityEventType }): Promise<ActivityLogEntry[]> {
let sql = "SELECT * FROM activityLog WHERE 1=1";
const params: any[] = [];
const params: (string | number)[] = [];
if (options?.since) {
sql += " AND timestamp > ?";
@@ -5261,7 +5375,7 @@ ${notificationsSection}`;
params.push(options.limit);
}
const rows = this.db.prepare(sql).all(...params) as any[];
const rows = this.db.prepare(sql).all(...params) as unknown as ActivityLogRow[];
return rows.map((row) => ({
id: row.id,
timestamp: row.timestamp,