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:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ───────────────────────────────────
|
||||
|
||||
@@ -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 = ?");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user