@cardor/agent-harness-kit 1.11.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -10
- package/dist/agent-templates/builder.md +20 -14
- package/dist/agent-templates/explorer.md +11 -8
- package/dist/agent-templates/lead.md +10 -7
- package/dist/agent-templates/reviewer.md +9 -5
- package/dist/{chunk-6PEIJ2D5.js → chunk-JTACLEGM.js} +45 -16
- package/dist/chunk-JTACLEGM.js.map +1 -0
- package/dist/chunk-URMVLD2S.js +147 -0
- package/dist/chunk-URMVLD2S.js.map +1 -0
- package/dist/cli.js +770 -407
- package/dist/cli.js.map +1 -1
- package/dist/{db-3OXHRFAR.js → db-L3AADJF5.js} +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/{mysql-THKQOXIS.js → mysql-AUPKARWA.js} +11 -5
- package/dist/mysql-AUPKARWA.js.map +1 -0
- package/dist/{postgres-IOQE32DM.js → postgres-BB4GY4PN.js} +11 -5
- package/dist/postgres-BB4GY4PN.js.map +1 -0
- package/dist/{sqlite-TR4D324R.js → sqlite-5OWKTUUZ.js} +11 -5
- package/dist/sqlite-5OWKTUUZ.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-6PEIJ2D5.js.map +0 -1
- package/dist/mysql-THKQOXIS.js.map +0 -1
- package/dist/postgres-IOQE32DM.js.map +0 -1
- package/dist/sqlite-TR4D324R.js.map +0 -1
- /package/dist/{db-3OXHRFAR.js.map → db-L3AADJF5.js.map} +0 -0
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
isEmptyDatabase,
|
|
8
8
|
openDB,
|
|
9
9
|
readStorageStateFile,
|
|
10
|
+
resetAutoincrementSequences,
|
|
10
11
|
resolveGlobalStorageDir,
|
|
11
12
|
resolveMarkdownFallbackPath,
|
|
12
13
|
resolveSqlitePath,
|
|
13
14
|
resolveSqlitePathForScope,
|
|
14
15
|
writeStorageStateFile
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JTACLEGM.js";
|
|
16
17
|
export {
|
|
17
18
|
DEFAULT_MARKDOWN_PATH,
|
|
18
19
|
DEFAULT_SQLITE_PATH,
|
|
@@ -22,10 +23,11 @@ export {
|
|
|
22
23
|
isEmptyDatabase,
|
|
23
24
|
openDB,
|
|
24
25
|
readStorageStateFile,
|
|
26
|
+
resetAutoincrementSequences,
|
|
25
27
|
resolveGlobalStorageDir,
|
|
26
28
|
resolveMarkdownFallbackPath,
|
|
27
29
|
resolveSqlitePath,
|
|
28
30
|
resolveSqlitePathForScope,
|
|
29
31
|
writeStorageStateFile
|
|
30
32
|
};
|
|
31
|
-
//# sourceMappingURL=db-
|
|
33
|
+
//# sourceMappingURL=db-L3AADJF5.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ interface TaskRow {
|
|
|
109
109
|
type AgentName = 'lead' | 'explorer' | 'consultant' | 'builder' | 'reviewer' | `custom:${string}`;
|
|
110
110
|
type ActionStatus = 'in_progress' | 'completed' | 'blocked';
|
|
111
111
|
interface ActionRow {
|
|
112
|
-
id:
|
|
112
|
+
id: number;
|
|
113
113
|
task_id: number;
|
|
114
114
|
agent: AgentName;
|
|
115
115
|
status: ActionStatus;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
migrateActionsToIntegerIds
|
|
3
|
+
} from "./chunk-URMVLD2S.js";
|
|
4
|
+
import "./chunk-JTACLEGM.js";
|
|
5
|
+
|
|
1
6
|
// src/core/drivers/mysql.ts
|
|
2
7
|
import mysql from "mysql2/promise";
|
|
3
8
|
var SCHEMA = `
|
|
@@ -25,7 +30,7 @@ CREATE TABLE IF NOT EXISTS task_acceptance (
|
|
|
25
30
|
);
|
|
26
31
|
|
|
27
32
|
CREATE TABLE IF NOT EXISTS actions (
|
|
28
|
-
id
|
|
33
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
29
34
|
task_id INT NOT NULL,
|
|
30
35
|
agent VARCHAR(100) NOT NULL,
|
|
31
36
|
status VARCHAR(20) NOT NULL DEFAULT 'in_progress'
|
|
@@ -38,7 +43,7 @@ CREATE TABLE IF NOT EXISTS actions (
|
|
|
38
43
|
|
|
39
44
|
CREATE TABLE IF NOT EXISTS action_sections (
|
|
40
45
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
41
|
-
action_id
|
|
46
|
+
action_id INT NOT NULL,
|
|
42
47
|
section_type VARCHAR(100) NOT NULL,
|
|
43
48
|
content TEXT NOT NULL,
|
|
44
49
|
created_at VARCHAR(30) NOT NULL,
|
|
@@ -47,7 +52,7 @@ CREATE TABLE IF NOT EXISTS action_sections (
|
|
|
47
52
|
|
|
48
53
|
CREATE TABLE IF NOT EXISTS action_files (
|
|
49
54
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
50
|
-
action_id
|
|
55
|
+
action_id INT NOT NULL,
|
|
51
56
|
file_path VARCHAR(1000) NOT NULL,
|
|
52
57
|
operation VARCHAR(20) NOT NULL
|
|
53
58
|
CHECK(operation IN ('read','created','modified','deleted')),
|
|
@@ -57,7 +62,7 @@ CREATE TABLE IF NOT EXISTS action_files (
|
|
|
57
62
|
|
|
58
63
|
CREATE TABLE IF NOT EXISTS action_tools (
|
|
59
64
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
60
|
-
action_id
|
|
65
|
+
action_id INT NOT NULL,
|
|
61
66
|
tool_name VARCHAR(255) NOT NULL,
|
|
62
67
|
args_json TEXT,
|
|
63
68
|
result_summary TEXT,
|
|
@@ -96,6 +101,7 @@ var MySQLDriver = class {
|
|
|
96
101
|
} finally {
|
|
97
102
|
conn.release();
|
|
98
103
|
}
|
|
104
|
+
await migrateActionsToIntegerIds(this, "mysql", SCHEMA);
|
|
99
105
|
}
|
|
100
106
|
async query(sql, params = []) {
|
|
101
107
|
const [rows] = await this.pool.execute(sql, params);
|
|
@@ -173,4 +179,4 @@ var MySQLTxDriver = class {
|
|
|
173
179
|
export {
|
|
174
180
|
MySQLDriver
|
|
175
181
|
};
|
|
176
|
-
//# sourceMappingURL=mysql-
|
|
182
|
+
//# sourceMappingURL=mysql-AUPKARWA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/drivers/mysql.ts"],"sourcesContent":["import mysql, { type ExecuteValues } from 'mysql2/promise'\n\nimport { migrateActionsToIntegerIds } from './migrate-actions'\n\nimport type { DBDriver } from './types'\nimport type { RemoteDBConfig } from '@/types'\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS tasks (\n id INT AUTO_INCREMENT PRIMARY KEY,\n slug VARCHAR(255) NOT NULL UNIQUE,\n title VARCHAR(500) NOT NULL,\n description TEXT,\n status VARCHAR(20) NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending','in_progress','done','blocked')),\n assigned_to VARCHAR(255),\n created_at VARCHAR(30) NOT NULL,\n started_at VARCHAR(30),\n completed_at VARCHAR(30),\n archived_at VARCHAR(30),\n updated_at VARCHAR(30) NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS task_acceptance (\n id INT AUTO_INCREMENT PRIMARY KEY,\n task_id INT NOT NULL,\n criterion TEXT NOT NULL,\n met INT NOT NULL DEFAULT 0,\n FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS actions (\n id INT AUTO_INCREMENT PRIMARY KEY,\n task_id INT NOT NULL,\n agent VARCHAR(100) NOT NULL,\n status VARCHAR(20) NOT NULL DEFAULT 'in_progress'\n CHECK(status IN ('in_progress','completed','blocked')),\n created_at VARCHAR(30) NOT NULL,\n completed_at VARCHAR(30),\n summary TEXT,\n FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS action_sections (\n id INT AUTO_INCREMENT PRIMARY KEY,\n action_id INT NOT NULL,\n section_type VARCHAR(100) NOT NULL,\n content TEXT NOT NULL,\n created_at VARCHAR(30) NOT NULL,\n FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS action_files (\n id INT AUTO_INCREMENT PRIMARY KEY,\n action_id INT NOT NULL,\n file_path VARCHAR(1000) NOT NULL,\n operation VARCHAR(20) NOT NULL\n CHECK(operation IN ('read','created','modified','deleted')),\n notes TEXT,\n FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS action_tools (\n id INT AUTO_INCREMENT PRIMARY KEY,\n action_id INT NOT NULL,\n tool_name VARCHAR(255) NOT NULL,\n args_json TEXT,\n result_summary TEXT,\n called_at VARCHAR(30) NOT NULL,\n FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_actions_task_id ON actions(task_id);\nCREATE INDEX IF NOT EXISTS idx_actions_agent ON actions(agent);\nCREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);\nCREATE INDEX IF NOT EXISTS idx_action_files_path ON action_files(file_path(255));\nCREATE INDEX IF NOT EXISTS idx_action_tools_name ON action_tools(tool_name);\n`\n\ntype MySQLPool = mysql.Pool\n\nexport class MySQLDriver implements DBDriver {\n private pool: MySQLPool\n\n constructor(config: RemoteDBConfig) {\n this.pool = mysql.createPool(config.connectionString)\n }\n\n async ensureSchema(): Promise<void> {\n // Run each statement individually since mysql2 doesn't support multi-statement by default\n const statements = SCHEMA.split(';')\n .map((s) => s.trim())\n .filter(Boolean)\n\n const conn = await this.pool.getConnection()\n try {\n for (const stmt of statements) {\n await conn.execute(stmt)\n }\n // Migration: add archived_at column (safe to run multiple times)\n try {\n await conn.execute('ALTER TABLE tasks ADD COLUMN archived_at VARCHAR(30)')\n } catch {\n // Column already exists — ignore\n }\n // Migration: add updated_at column (safe to run multiple times)\n try {\n await conn.execute(`ALTER TABLE tasks ADD COLUMN updated_at VARCHAR(30) NOT NULL DEFAULT CURRENT_TIMESTAMP`)\n await conn.execute(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`)\n } catch {\n // Column already exists — ignore\n }\n } finally {\n conn.release()\n }\n // Migration (task #73): actions.id VARCHAR/UUID -> INT AUTO_INCREMENT,\n // preserving all existing rows. Idempotent — no-op once already migrated.\n // Run outside the connection above (own execRaw/query calls via the pool)\n // since MySQL's DDL isn't transactional either way.\n await migrateActionsToIntegerIds(this, 'mysql', SCHEMA)\n }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const [rows] = await this.pool.execute(sql, params as ExecuteValues)\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n const rows = await this.query<T>(sql, params)\n return (rows as T[])[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.pool.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).insertId\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.pool.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).affectedRows\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.pool.query(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n const conn = await this.pool.getConnection()\n await conn.beginTransaction()\n try {\n const txDriver = new MySQLTxDriver(conn)\n const result = await fn(txDriver)\n await conn.commit()\n return result\n } catch (err) {\n await conn.rollback()\n throw err\n } finally {\n conn.release()\n }\n }\n\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n\n async close(): Promise<void> {\n await this.pool.end()\n }\n}\n\nclass MySQLTxDriver implements DBDriver {\n constructor(private conn: mysql.PoolConnection) { }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const [rows] = await this.conn.execute(sql, params as ExecuteValues)\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n return (await this.query<T>(sql, params))[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.conn.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).insertId\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.conn.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).affectedRows\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.conn.query(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n return fn(this)\n }\n\n async ensureSchema(): Promise<void> { }\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n async close(): Promise<void> { }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
migrateActionsToIntegerIds
|
|
3
|
+
} from "./chunk-URMVLD2S.js";
|
|
4
|
+
import "./chunk-JTACLEGM.js";
|
|
5
|
+
|
|
1
6
|
// src/core/drivers/postgres.ts
|
|
2
7
|
import postgres from "postgres";
|
|
3
8
|
var SCHEMA = `
|
|
@@ -24,7 +29,7 @@ CREATE TABLE IF NOT EXISTS task_acceptance (
|
|
|
24
29
|
);
|
|
25
30
|
|
|
26
31
|
CREATE TABLE IF NOT EXISTS actions (
|
|
27
|
-
id
|
|
32
|
+
id SERIAL PRIMARY KEY,
|
|
28
33
|
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
29
34
|
agent TEXT NOT NULL
|
|
30
35
|
CHECK(agent IN ('lead','explorer','builder','reviewer') OR agent LIKE 'custom:%'),
|
|
@@ -37,7 +42,7 @@ CREATE TABLE IF NOT EXISTS actions (
|
|
|
37
42
|
|
|
38
43
|
CREATE TABLE IF NOT EXISTS action_sections (
|
|
39
44
|
id SERIAL PRIMARY KEY,
|
|
40
|
-
action_id
|
|
45
|
+
action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
|
|
41
46
|
section_type TEXT NOT NULL,
|
|
42
47
|
content TEXT NOT NULL,
|
|
43
48
|
created_at TEXT NOT NULL
|
|
@@ -45,7 +50,7 @@ CREATE TABLE IF NOT EXISTS action_sections (
|
|
|
45
50
|
|
|
46
51
|
CREATE TABLE IF NOT EXISTS action_files (
|
|
47
52
|
id SERIAL PRIMARY KEY,
|
|
48
|
-
action_id
|
|
53
|
+
action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
|
|
49
54
|
file_path TEXT NOT NULL,
|
|
50
55
|
operation TEXT NOT NULL
|
|
51
56
|
CHECK(operation IN ('read','created','modified','deleted')),
|
|
@@ -54,7 +59,7 @@ CREATE TABLE IF NOT EXISTS action_files (
|
|
|
54
59
|
|
|
55
60
|
CREATE TABLE IF NOT EXISTS action_tools (
|
|
56
61
|
id SERIAL PRIMARY KEY,
|
|
57
|
-
action_id
|
|
62
|
+
action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
|
|
58
63
|
tool_name TEXT NOT NULL,
|
|
59
64
|
args_json TEXT,
|
|
60
65
|
result_summary TEXT,
|
|
@@ -88,6 +93,7 @@ var PostgresDriver = class {
|
|
|
88
93
|
await this.sql.unsafe(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`);
|
|
89
94
|
} catch {
|
|
90
95
|
}
|
|
96
|
+
await migrateActionsToIntegerIds(this, "postgres", SCHEMA);
|
|
91
97
|
}
|
|
92
98
|
async query(sql, params = []) {
|
|
93
99
|
const rows = await this.sql.unsafe(toPositional(sql), params);
|
|
@@ -160,4 +166,4 @@ var PostgresTxDriver = class {
|
|
|
160
166
|
export {
|
|
161
167
|
PostgresDriver
|
|
162
168
|
};
|
|
163
|
-
//# sourceMappingURL=postgres-
|
|
169
|
+
//# sourceMappingURL=postgres-BB4GY4PN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/drivers/postgres.ts"],"sourcesContent":["import postgres from 'postgres'\n\nimport { migrateActionsToIntegerIds } from './migrate-actions'\n\nimport type { DBDriver } from './types'\nimport type { RemoteDBConfig } from '@/types'\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS tasks (\n id SERIAL PRIMARY KEY,\n slug TEXT NOT NULL UNIQUE,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending','in_progress','done','blocked')),\n assigned_to TEXT,\n created_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n archived_at TEXT,\n updated_at TEXT NOT NULL DEFAULT NOW()\n);\n\nCREATE TABLE IF NOT EXISTS task_acceptance (\n id SERIAL PRIMARY KEY,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n criterion TEXT NOT NULL,\n met INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS actions (\n id SERIAL PRIMARY KEY,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n agent TEXT NOT NULL\n CHECK(agent IN ('lead','explorer','builder','reviewer') OR agent LIKE 'custom:%'),\n status TEXT NOT NULL DEFAULT 'in_progress'\n CHECK(status IN ('in_progress','completed','blocked')),\n created_at TEXT NOT NULL,\n completed_at TEXT,\n summary TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_sections (\n id SERIAL PRIMARY KEY,\n action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n section_type TEXT NOT NULL,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS action_files (\n id SERIAL PRIMARY KEY,\n action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n file_path TEXT NOT NULL,\n operation TEXT NOT NULL\n CHECK(operation IN ('read','created','modified','deleted')),\n notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_tools (\n id SERIAL PRIMARY KEY,\n action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n tool_name TEXT NOT NULL,\n args_json TEXT,\n result_summary TEXT,\n called_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_actions_task_id ON actions(task_id);\nCREATE INDEX IF NOT EXISTS idx_actions_agent ON actions(agent);\nCREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);\nCREATE INDEX IF NOT EXISTS idx_action_files_path ON action_files(file_path);\nCREATE INDEX IF NOT EXISTS idx_action_tools_name ON action_tools(tool_name);\n`\n\n// Convert SQLite-style ? placeholders to Postgres $1, $2, ...\nfunction toPositional(sql: string): string {\n let i = 0\n return sql.replace(/\\?/g, () => `$${++i}`)\n}\n\nexport class PostgresDriver implements DBDriver {\n private sql: postgres.Sql\n\n constructor(config: RemoteDBConfig) {\n this.sql = postgres(config.connectionString)\n }\n\n async ensureSchema(): Promise<void> {\n await this.sql.unsafe(SCHEMA)\n // Migration: add archived_at column (safe to run multiple times)\n try {\n await this.sql.unsafe(`ALTER TABLE tasks ADD COLUMN archived_at TEXT`)\n } catch {\n // Column already exists — ignore\n }\n // Migration: add updated_at column (safe to run multiple times)\n try {\n await this.sql.unsafe(`ALTER TABLE tasks ADD COLUMN updated_at TEXT NOT NULL DEFAULT NOW()`)\n await this.sql.unsafe(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`)\n } catch {\n // Column already exists — ignore\n }\n // Migration (task #73): actions.id TEXT/UUID -> INTEGER autoincrement,\n // preserving all existing rows. Idempotent — no-op once already migrated.\n await migrateActionsToIntegerIds(this, 'postgres', SCHEMA)\n }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const rows = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n const rows = await this.query<T>(sql, params)\n return rows[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const withReturning = toPositional(sql.trimEnd()) + ' RETURNING id'\n const rows = await this.sql.unsafe(withReturning, params as postgres.ParameterOrJSON<never>[])\n return (rows[0] as unknown as { id: number }).id\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const result = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return result.count\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.sql.unsafe(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n let result!: T\n await this.sql.begin(async (txSql) => {\n const txDriver = new PostgresTxDriver(txSql)\n result = await fn(txDriver)\n })\n return result\n }\n\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n\n async close(): Promise<void> {\n await this.sql.end()\n }\n}\n\nclass PostgresTxDriver implements DBDriver {\n constructor(private sql: postgres.TransactionSql) { }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const rows = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n return (await this.query<T>(sql, params))[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const withReturning = toPositional(sql.trimEnd()) + ' RETURNING id'\n const rows = await this.sql.unsafe(withReturning, params as postgres.ParameterOrJSON<never>[])\n return (rows[0] as unknown as { id: number }).id\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const result = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return result.count\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.sql.unsafe(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n return fn(this)\n }\n\n async ensureSchema(): Promise<void> { }\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n async close(): Promise<void> { }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
migrateActionsToIntegerIds
|
|
3
|
+
} from "./chunk-URMVLD2S.js";
|
|
4
|
+
import "./chunk-JTACLEGM.js";
|
|
5
|
+
|
|
1
6
|
// src/core/drivers/sqlite.ts
|
|
2
7
|
import { existsSync, mkdirSync, rmSync, statSync } from "fs";
|
|
3
8
|
import { dirname } from "path";
|
|
@@ -44,7 +49,7 @@ CREATE TABLE IF NOT EXISTS task_acceptance (
|
|
|
44
49
|
);
|
|
45
50
|
|
|
46
51
|
CREATE TABLE IF NOT EXISTS actions (
|
|
47
|
-
id
|
|
52
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
48
53
|
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
49
54
|
agent TEXT NOT NULL
|
|
50
55
|
CHECK(agent IN ('lead','explorer','builder','reviewer') OR agent LIKE 'custom:%'),
|
|
@@ -57,7 +62,7 @@ CREATE TABLE IF NOT EXISTS actions (
|
|
|
57
62
|
|
|
58
63
|
CREATE TABLE IF NOT EXISTS action_sections (
|
|
59
64
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
|
-
action_id
|
|
65
|
+
action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
|
|
61
66
|
section_type TEXT NOT NULL,
|
|
62
67
|
content TEXT NOT NULL,
|
|
63
68
|
created_at TEXT NOT NULL
|
|
@@ -65,7 +70,7 @@ CREATE TABLE IF NOT EXISTS action_sections (
|
|
|
65
70
|
|
|
66
71
|
CREATE TABLE IF NOT EXISTS action_files (
|
|
67
72
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
-
action_id
|
|
73
|
+
action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
|
|
69
74
|
file_path TEXT NOT NULL,
|
|
70
75
|
operation TEXT NOT NULL
|
|
71
76
|
CHECK(operation IN ('read','created','modified','deleted')),
|
|
@@ -74,7 +79,7 @@ CREATE TABLE IF NOT EXISTS action_files (
|
|
|
74
79
|
|
|
75
80
|
CREATE TABLE IF NOT EXISTS action_tools (
|
|
76
81
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
77
|
-
action_id
|
|
82
|
+
action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
|
|
78
83
|
tool_name TEXT NOT NULL,
|
|
79
84
|
args_json TEXT,
|
|
80
85
|
result_summary TEXT,
|
|
@@ -117,6 +122,7 @@ var SQLiteDriver = class {
|
|
|
117
122
|
this.db.exec(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`);
|
|
118
123
|
} catch {
|
|
119
124
|
}
|
|
125
|
+
await migrateActionsToIntegerIds(this, "sqlite", SCHEMA);
|
|
120
126
|
}
|
|
121
127
|
async query(sql, params = []) {
|
|
122
128
|
return this.db.prepare(sql).all(...params);
|
|
@@ -159,4 +165,4 @@ var SQLiteDriver = class {
|
|
|
159
165
|
export {
|
|
160
166
|
SQLiteDriver
|
|
161
167
|
};
|
|
162
|
-
//# sourceMappingURL=sqlite-
|
|
168
|
+
//# sourceMappingURL=sqlite-5OWKTUUZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/drivers/sqlite.ts","../src/core/sqlite-adapter.ts"],"sourcesContent":["import { existsSync, mkdirSync, rmSync, statSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\nimport { lastInsertId, openSQLite, type SQLiteDB } from '../sqlite-adapter'\nimport { migrateActionsToIntegerIds } from './migrate-actions'\n\nimport type { DBDriver } from './types'\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n slug TEXT NOT NULL UNIQUE,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending','in_progress','done','blocked')),\n assigned_to TEXT,\n created_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n archived_at TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS task_acceptance (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n criterion TEXT NOT NULL,\n met INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS actions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n agent TEXT NOT NULL\n CHECK(agent IN ('lead','explorer','builder','reviewer') OR agent LIKE 'custom:%'),\n status TEXT NOT NULL DEFAULT 'in_progress'\n CHECK(status IN ('in_progress','completed','blocked')),\n created_at TEXT NOT NULL,\n completed_at TEXT,\n summary TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_sections (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n section_type TEXT NOT NULL,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS action_files (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n file_path TEXT NOT NULL,\n operation TEXT NOT NULL\n CHECK(operation IN ('read','created','modified','deleted')),\n notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_tools (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n action_id INTEGER NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n tool_name TEXT NOT NULL,\n args_json TEXT,\n result_summary TEXT,\n called_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_actions_task_id ON actions(task_id);\nCREATE INDEX IF NOT EXISTS idx_actions_agent ON actions(agent);\nCREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);\nCREATE INDEX IF NOT EXISTS idx_action_files_path ON action_files(file_path);\nCREATE INDEX IF NOT EXISTS idx_action_tools_name ON action_tools(tool_name);\n`\n\nexport class SQLiteDriver implements DBDriver {\n private db: SQLiteDB\n private dbPath: string\n\n constructor(dbPath: string) {\n this.dbPath = dbPath\n mkdirSync(dirname(dbPath), { recursive: true })\n // Remove stale WAL/SHM files left by a crashed session — they cause SQLITE_IOERR.\n // A 0-byte WAL alongside a non-empty SHM means the last checkpoint never completed.\n if (existsSync(dbPath)) {\n const shm = `${dbPath}-shm`\n const wal = `${dbPath}-wal`\n if (existsSync(shm) && existsSync(wal) && statSync(wal).size === 0) {\n rmSync(shm, { force: true })\n rmSync(wal, { force: true })\n }\n }\n this.db = openSQLite(dbPath)\n this.db.exec('PRAGMA journal_mode = WAL')\n this.db.exec('PRAGMA foreign_keys = ON')\n }\n\n async ensureSchema(): Promise<void> {\n this.db.exec(SCHEMA)\n // Migration: add archived_at column (safe to run multiple times)\n try {\n this.db.exec('ALTER TABLE tasks ADD COLUMN archived_at TEXT')\n } catch {\n // Column already exists — ignore\n }\n // Migration: add updated_at column (safe to run multiple times)\n try {\n this.db.exec(`ALTER TABLE tasks ADD COLUMN updated_at TEXT`)\n this.db.exec(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`)\n } catch {\n // Column already exists — ignore\n }\n // Migration (task #73): actions.id TEXT/UUID -> INTEGER autoincrement,\n // preserving all existing rows. Idempotent — no-op once already migrated.\n await migrateActionsToIntegerIds(this, 'sqlite', SCHEMA)\n }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n return this.db.prepare(sql).all(...params) as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n return (this.db.prepare(sql).get(...params) as unknown as T) ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n this.db.prepare(sql).run(...params)\n return lastInsertId(this.db)\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const result = this.db.prepare(sql).run(...params)\n return (result as { changes?: number }).changes ?? 0\n }\n\n async execRaw(sql: string): Promise<void> {\n this.db.exec(sql)\n }\n\n async reconnect(): Promise<void> {\n this.db.close()\n this.db = openSQLite(this.dbPath)\n this.db.exec('PRAGMA journal_mode = WAL')\n this.db.exec('PRAGMA foreign_keys = ON')\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n this.db.exec('BEGIN IMMEDIATE')\n try {\n const result = await fn(this)\n this.db.exec('COMMIT')\n return result\n } catch (err) {\n this.db.exec('ROLLBACK')\n throw err\n }\n }\n\n async close(): Promise<void> {\n this.db.close()\n }\n}\n","import { createRequire } from 'node:module'\n\nconst _require = createRequire(import.meta.url)\nconst isBun = 'bun' in process.versions\n\n// ─── Shared interface ─────────────────────────────────────────────────────────\n// Both better-sqlite3 (Database) and bun:sqlite (Database) expose this surface.\n\nexport type SQLRow = Record<string, unknown>\n\nexport interface SQLStatement {\n run(...args: unknown[]): unknown\n get(...args: unknown[]): SQLRow | undefined\n all(...args: unknown[]): SQLRow[]\n}\n\nexport interface SQLiteDB {\n exec(sql: string): void\n prepare(sql: string): SQLStatement\n close(): void\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function openSQLite(path: string): SQLiteDB {\n if (isBun) {\n const { Database } = _require('bun:sqlite') as {\n Database: new (path: string) => unknown\n }\n return new Database(path) as unknown as SQLiteDB\n }\n\n const Database = _require('better-sqlite3') as new (path: string) => unknown\n return new Database(path) as unknown as SQLiteDB\n}\n\n// ─── last_insert_rowid() helper ───────────────────────────────────────────────\n// Both bun:sqlite and better-sqlite3 have different return types for stmt.run().\n// Reading last_insert_rowid() directly avoids the inconsistency.\n\nexport function lastInsertId(db: SQLiteDB): number {\n const row = db.prepare('SELECT last_insert_rowid() AS id').get() as { id: number }\n return row.id\n}\n"],"mappings":"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","names":["Database"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardor/agent-harness-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CLI scaffolding for multi-agent harness systems",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"dev": "tsup --watch",
|
|
80
80
|
"test": "node --test --import tsx/esm src/tests/*.test.ts",
|
|
81
81
|
"lint": "eslint . --fix",
|
|
82
|
+
"lint:check": "eslint src/",
|
|
82
83
|
"format": "prettier --write src dashboard scripts",
|
|
83
84
|
"release": "bash scripts/publish.sh",
|
|
84
85
|
"release:dry": "bash scripts/publish.sh --dry-run"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/db.ts","../src/core/repositories/ActionRepository.ts","../src/core/repositories/StatsRepository.ts","../src/core/repositories/TaskRepository.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\n\nimport { ActionRepository } from './repositories/ActionRepository'\nimport { StatsRepository } from './repositories/StatsRepository'\nimport { TaskRepository } from './repositories/TaskRepository'\n\nimport type { DBDriver } from './drivers/types'\nimport type {\n ActionFileRow,\n ActionRow,\n ActionSectionRow,\n ActionToolRow,\n AgentName,\n HarnessConfig,\n StorageState,\n TaskAcceptanceRow,\n TaskRow,\n TaskStatus,\n} from '@/types'\n\n/** Full relational export of every table — used by `ahk migrate storage` and\n * `ahk export --json`. MUST include all 6 tables (tasks, task_acceptance,\n * actions, action_sections, action_files, action_tools); omitting any of\n * them silently drops user data during a migration. */\nexport interface FullExport {\n tasks: TaskRow[]\n taskAcceptance: TaskAcceptanceRow[]\n actions: ActionRow[]\n sections: ActionSectionRow[]\n actionFiles: ActionFileRow[]\n actionTools: ActionToolRow[]\n}\n\n/** Tables with an integer autoincrement/serial primary key, in FK-safe\n * insertion order (parents before children). `actions` is excluded — its id\n * is an application-generated UUID (TEXT), never autoincrement. */\nconst AUTOINCREMENT_TABLES = ['tasks', 'task_acceptance', 'action_sections', 'action_files', 'action_tools'] as const\n\n/** Full insertion order across all 6 tables, respecting FK constraints\n * (parent before child): tasks -> task_acceptance -> actions ->\n * action_sections/action_files/action_tools. */\nconst TABLE_INSERT_ORDER = ['tasks', 'task_acceptance', 'actions', 'action_sections', 'action_files', 'action_tools'] as const\n\n/** Reverse of TABLE_INSERT_ORDER — used to TRUNCATE a non-empty destination\n * safely (children before parents) when `--force` is used. */\nconst TABLE_DELETE_ORDER = [...TABLE_INSERT_ORDER].reverse()\n\n// ─── Global storage path resolution ────────────────────────────────────────\n\n/** Default relative sqlite path used whenever `LocalStorageConfig.sqlitePath`\n * is omitted, and as the fallback filename component for `scope: 'global'`\n * (which never reads a configured path at all — see resolveSqlitePath()).\n * Centralized here (task #56) to stop the literal '.harness/harness.db'\n * from drifting across config.ts/templates.ts/tests. */\nexport const DEFAULT_SQLITE_PATH = '.harness/harness.db'\n\n/** Default relative current.md fallback path, used as the fallback filename\n * component whenever a caller needs \"the local markdown path\" for a scope\n * that ISN'T `config.storage`'s own current scope — `markdownFallback.path`\n * only exists on `LocalStorageConfig`, so there's no field to read it from\n * when `config.storage.scope === 'global'`. See `defaultMarkdownPathForConfig`\n * usage in src/commands/migrate-storage.ts. */\nexport const DEFAULT_MARKDOWN_PATH = '.harness/current.md'\n\n/** Resolves the directory used for 'global' scope storage: ~/.harness/dbs/<projectId>/\n * Uses os.homedir() (not $HOME env var) for portability. Callers are\n * responsible for creating the directory (mkdirSync recursive) before use. */\nexport function resolveGlobalStorageDir(config: HarnessConfig, homeDir: string = homedir()): string {\n return join(homeDir, '.harness', 'dbs', config.storage.projectId)\n}\n\n// ─── DB class ─────────────────────────────────────────────────────────────────\n\nexport class HarnessDB {\n readonly tasks: TaskRepository\n readonly actions: ActionRepository\n readonly stats: StatsRepository\n private driver: DBDriver\n private config: HarnessConfig\n /** Overridable home directory, used to keep 'global' scope tests off the real $HOME. */\n private homeDir: string\n\n constructor(driver: DBDriver, config: HarnessConfig, homeDir: string = homedir()) {\n this.driver = driver\n this.config = config\n this.homeDir = homeDir\n this.tasks = new TaskRepository(driver)\n this.actions = new ActionRepository(driver)\n this.stats = new StatsRepository(driver)\n }\n\n // ─── Tasks (public facade — delegates to TaskRepository) ──────────────────\n\n async addTask(params: {\n slug: string\n title: string\n description?: string\n acceptance?: string[]\n }): Promise<TaskRow> {\n const taskId = await this.tasks.add({\n slug: params.slug,\n title: params.title,\n description: params.description,\n })\n if (params.acceptance?.length) {\n await this.tasks.addAcceptance(taskId, params.acceptance)\n }\n await this.regenerateCurrentMd()\n return (await this.tasks.getById(taskId))!\n }\n\n async getTasks(status?: TaskStatus, includeArchived = false): Promise<TaskRow[]> {\n return this.tasks.getAll(status, includeArchived)\n }\n\n async getTaskById(id: number): Promise<TaskRow | null> {\n return this.tasks.getById(id)\n }\n\n async getTaskBySlug(slug: string): Promise<TaskRow | null> {\n return this.tasks.getBySlug(slug)\n }\n\n async getTaskAcceptance(taskId: number): Promise<TaskAcceptanceRow[]> {\n return this.tasks.getAcceptance(taskId)\n }\n\n async updateTaskStatus(idOrSlug: number | string, status: TaskStatus): Promise<TaskRow> {\n const now = new Date().toISOString()\n const task =\n typeof idOrSlug === 'number'\n ? await this.tasks.getById(idOrSlug)\n : await this.tasks.getBySlug(idOrSlug)\n if (!task) throw new Error(`Task not found: ${idOrSlug}`)\n\n if (status === 'in_progress' && !task.started_at) {\n await this.tasks.setStatus(task.id, status, { started_at: now })\n } else if (status === 'done') {\n await this.tasks.setStatus(task.id, status, { completed_at: now })\n } else {\n await this.tasks.setStatus(task.id, status)\n }\n\n await this.regenerateCurrentMd()\n return (await this.tasks.getById(task.id))!\n }\n\n async claimTask(id: number, agent: string): Promise<TaskRow | null> {\n const now = new Date().toISOString()\n return this.driver.transaction(async (tx) => {\n // need to create a new TaskRepository instance bound to the transaction\n const txTasks = new TaskRepository(tx)\n const changed = await txTasks.claim(id, agent, now)\n if (!changed) return null\n const task = await txTasks.getById(id)\n if (!task || task.status !== 'in_progress' || task.assigned_to !== agent) return null\n await this.regenerateCurrentMd()\n return task\n })\n }\n\n async markAcceptanceMet(criterionId: number): Promise<void> {\n return this.tasks.markAcceptanceMet(criterionId)\n }\n\n async updateTask(id: number, params: { title?: string; description?: string | null; slug?: string }): Promise<TaskRow> {\n await this.tasks.update(id, params)\n await this.regenerateCurrentMd()\n return (await this.tasks.getById(id))!\n }\n\n async updateTaskAcceptance(taskId: number, criteria: string[]): Promise<void> {\n await this.tasks.replaceAcceptance(taskId, criteria)\n await this.regenerateCurrentMd()\n }\n\n async archiveTask(id: number): Promise<TaskRow> {\n await this.tasks.archive(id)\n await this.regenerateCurrentMd()\n return (await this.tasks.getById(id))!\n }\n\n async unarchiveTask(id: number): Promise<TaskRow> {\n await this.tasks.unarchive(id)\n await this.regenerateCurrentMd()\n return (await this.tasks.getById(id))!\n }\n\n async getArchivedTasks(): Promise<TaskRow[]> {\n return this.tasks.getArchived()\n }\n\n async getStatusSummary(): Promise<{ status: string; total: number }[]> {\n return this.tasks.getStatusSummary()\n }\n\n // ─── Actions (public facade — delegates to ActionRepository) ──────────────\n\n async startAction(taskId: number, agent: AgentName): Promise<ActionRow> {\n const id = randomUUID()\n const now = new Date().toISOString()\n await this.actions.create(id, taskId, agent, now)\n await this.regenerateCurrentMd()\n return (await this.actions.getById(id))!\n }\n\n async writeSection(actionId: string, sectionType: string, content: string): Promise<void> {\n const now = new Date().toISOString()\n await this.actions.addSection(actionId, sectionType, content, now)\n await this.regenerateCurrentMd()\n }\n\n async completeAction(actionId: string, summary: string): Promise<ActionRow> {\n const now = new Date().toISOString()\n await this.actions.complete(actionId, summary, now)\n await this.regenerateCurrentMd()\n return (await this.actions.getById(actionId))!\n }\n\n async closeOrphanedActions(taskId: number): Promise<number> {\n const now = new Date().toISOString()\n return this.actions.closeOrphaned(taskId, now)\n }\n\n async getAction(actionId: string): Promise<ActionRow | null> {\n return this.actions.getById(actionId)\n }\n\n async getActionsForTask(taskId: number): Promise<ActionRow[]> {\n return this.actions.getForTask(taskId)\n }\n\n async getActionSections(actionId: string): Promise<ActionSectionRow[]> {\n return this.actions.getSections(actionId)\n }\n\n async recordFile(\n actionId: string,\n filePath: string,\n operation: ActionFileRow['operation'],\n notes?: string,\n ): Promise<void> {\n return this.actions.addFile(actionId, filePath, operation, notes ?? null)\n }\n\n async recordTool(\n actionId: string,\n toolName: string,\n argsJson?: string,\n resultSummary?: string,\n ): Promise<void> {\n const now = new Date().toISOString()\n return this.actions.addTool(actionId, toolName, argsJson ?? null, resultSummary ?? null, now)\n }\n\n async getFilesForTask(taskId: number): Promise<(ActionFileRow & { agent: AgentName })[]> {\n return this.actions.getFilesForTask(taskId)\n }\n\n async getTopTools(limit = 10): Promise<{ tool_name: string; uses: number }[]> {\n return this.actions.getTopTools(limit)\n }\n\n // ─── current.md fallback ──────────────────────────────────────────────────\n\n async regenerateCurrentMd(): Promise<void> {\n if (!this.config.storage.markdownFallback.enabled) return\n\n const mdPath =\n this.config.storage.scope === 'global'\n ? join(resolveGlobalStorageDir(this.config, this.homeDir), 'current.md')\n : resolve(this.config.storage.markdownFallback.path)\n mkdirSync(dirname(mdPath), { recursive: true })\n\n const inProgress = await this.tasks.getAll('in_progress')\n const now = new Date().toISOString()\n\n let md = `<!-- AUTO-GENERATED by agent-harness-kit — DO NOT EDIT MANUALLY -->\\n`\n md += `<!-- Last updated: ${now} -->\\n\\n`\n md += `# Current Session\\n\\n`\n\n if (inProgress.length === 0) {\n md += `## No tasks in progress\\n\\n`\n const pending = await this.tasks.getAll('pending')\n if (pending.length > 0) {\n md += `### Next pending tasks\\n`\n for (const t of pending.slice(0, 5)) {\n md += `- **#${t.id}** ${t.title} (\\`${t.slug}\\`)\\n`\n }\n }\n } else {\n for (const task of inProgress) {\n md += `## Active Task\\n`\n md += `- **ID:** ${task.id}\\n`\n md += `- **Slug:** ${task.slug}\\n`\n md += `- **Status:** ${task.status}\\n`\n md += `- **Started:** ${task.started_at ?? 'unknown'}\\n\\n`\n\n const taskActions = await this.actions.getForTask(task.id)\n if (taskActions.length > 0) {\n md += `## Actions this session\\n`\n md += `| Agent | Status | Summary | Started |\\n`\n md += `|----------|-------------|----------------------------------|-------------|\\n`\n for (const a of taskActions) {\n const started = a.created_at.slice(11, 16)\n const summary = (a.summary ?? '').slice(0, 34).padEnd(34)\n md += `| ${a.agent.padEnd(8)} | ${a.status.padEnd(11)} | ${summary} | ${started} |\\n`\n }\n md += `\\n`\n }\n\n const acceptance = await this.tasks.getAcceptance(task.id)\n if (acceptance.length > 0) {\n md += `## Acceptance Criteria\\n`\n for (const a of acceptance) {\n md += `- [${a.met ? 'x' : ' '}] ${a.criterion}\\n`\n }\n md += `\\n`\n }\n }\n }\n\n writeFileSync(mdPath, md, 'utf8')\n }\n\n // ─── Raw query escape hatch ───────────────────────────────────────────────\n\n async queryRaw<T = Record<string, unknown>>(sql: string, ...params: unknown[]): Promise<T[]> {\n return this.driver.query<T>(sql, params)\n }\n\n // ─── Export helpers ───────────────────────────────────────────────────────\n\n /** Full relational export of ALL 6 tables (tasks, task_acceptance, actions,\n * action_sections, action_files, action_tools). Extended for task #47 —\n * the previous version (tasks/actions/sections only) silently dropped\n * acceptance criteria and file/tool records on export/migrate. */\n async exportJson(): Promise<FullExport> {\n return {\n tasks: await this.tasks.getAll(undefined, true),\n taskAcceptance: await this.tasks.getAllAcceptance(),\n actions: await this.actions.getAll(),\n sections: await this.actions.getAllSections(),\n actionFiles: await this.actions.getAllFiles(),\n actionTools: await this.actions.getAllTools(),\n }\n }\n\n /** Row counts for all 6 tables against THIS db's driver — used to decide\n * whether a destination is \"empty\" (safe to import into directly) before\n * a migration. Counts are queried directly (COUNT(*)), never inferred\n * from storage-state.json. */\n async getRowCounts(): Promise<Record<(typeof TABLE_INSERT_ORDER)[number], number>> {\n return getRowCounts(this.driver)\n }\n\n /** Imports a full export into THIS db's driver — see standalone\n * `importFullExport()` for the transactional/rollback/sequence-reset\n * guarantees. `dbType` must match `this.config.database.type`. */\n async importFullExport(data: FullExport, dbType: 'sqlite' | 'postgres' | 'mysql', opts?: { truncateFirst: boolean }): Promise<void> {\n return importFullExport(this.driver, data, dbType, opts)\n }\n\n async reconnect(): Promise<void> {\n await this.driver.reconnect()\n }\n\n async close(): Promise<void> {\n await this.driver.close()\n }\n\n // ─── feature_list.json sync ───────────────────────────────────────────────\n\n async syncFromFeatureList(\n seeds: { slug: string; title: string; description?: string; acceptance?: string[] }[],\n ): Promise<{ added: number; skipped: number }> {\n let added = 0\n let skipped = 0\n for (const t of seeds) {\n if (await this.tasks.getBySlug(t.slug)) {\n skipped++\n continue\n }\n await this.addTask(t)\n added++\n }\n return { added, skipped }\n }\n\n async writeFeatureList(cwd: string): Promise<void> {\n const allTasks = await this.tasks.getAll(undefined, true)\n const list = await Promise.all(\n allTasks.map(async (t) => ({\n slug: t.slug,\n title: t.title,\n description: t.description ?? undefined,\n acceptance: (await this.tasks.getAcceptance(t.id)).map((a) => a.criterion),\n status: t.status,\n })),\n )\n const path = join(resolve(cwd), this.config.storage.dir, 'feature_list.json')\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, JSON.stringify(list, null, 2) + '\\n', 'utf8')\n }\n\n // ─── storage-state.json (real storage state, for `ahk migrate storage`) ──\n\n /** Writes .harness/storage-state.json, ALWAYS project-local regardless of\n * scope. Reflects the REAL current storage state (scope/projectId/dbType\n * actually in use right now), as opposed to agent-harness-kit.config.ts\n * which reflects the DESIRED state. Format is stable — task #47 (ahk\n * migrate storage) depends on it; do not change field names/shape. */\n async writeStorageState(cwd: string): Promise<void> {\n writeStorageStateFile(cwd, this.config.storage.dir, {\n scope: this.config.storage.scope,\n projectId: this.config.storage.projectId,\n dbType: this.config.database.type,\n migratedAt: new Date().toISOString(),\n })\n }\n}\n\n// ─── Full DB migration helpers (task #47 — `ahk migrate storage`) ─────────\n\n/** Row counts for all 6 tables, queried directly (never inferred). Used to\n * decide whether a destination DB is \"empty\" before an sqlite↔remote\n * migration. */\nexport async function getRowCounts(driver: DBDriver): Promise<Record<(typeof TABLE_INSERT_ORDER)[number], number>> {\n const counts = {} as Record<(typeof TABLE_INSERT_ORDER)[number], number>\n for (const table of TABLE_INSERT_ORDER) {\n const row = await driver.queryOne<{ n: number }>(`SELECT COUNT(*) as n FROM ${table}`)\n counts[table] = Number(row?.n ?? 0)\n }\n return counts\n}\n\n/** True if every table is empty (COUNT(*) = 0 for all 6 tables). */\nexport async function isEmptyDatabase(driver: DBDriver): Promise<boolean> {\n const counts = await getRowCounts(driver)\n return Object.values(counts).every((n) => n === 0)\n}\n\n/** Deletes all rows from all 6 tables, children-before-parents, so FK\n * constraints never block the delete. Only ever called immediately before\n * a `--force` import, inside the same transaction as the import itself —\n * never on its own. */\nasync function truncateAllTables(tx: DBDriver): Promise<void> {\n for (const table of TABLE_DELETE_ORDER) {\n await tx.exec(`DELETE FROM ${table}`)\n }\n}\n\n/** Re-synchronizes the destination's internal autoincrement/serial counter\n * with the highest id actually present, AFTER inserting rows with explicit\n * ids. Required because inserting explicit ids does NOT advance\n * Postgres SERIAL sequences or SQLite's `sqlite_sequence` table — without\n * this, the first unrelated `INSERT ... (no id)` after a migration (e.g.\n * `tasksRepository.add()`) would collide with an imported id.\n * MySQL AUTO_INCREMENT advances automatically on explicit-id inserts\n * greater than the current counter — no action needed there. */\nasync function resetAutoincrementSequences(\n tx: DBDriver,\n dbType: 'sqlite' | 'postgres' | 'mysql',\n): Promise<void> {\n if (dbType === 'mysql') return // AUTO_INCREMENT self-advances on explicit-id insert — verified in tests.\n\n for (const table of AUTOINCREMENT_TABLES) {\n const row = await tx.queryOne<{ max: number | null }>(`SELECT MAX(id) as max FROM ${table}`)\n const max = row?.max\n if (!max) continue // table stayed empty — nothing to advance\n\n if (dbType === 'postgres') {\n await tx.execRaw(`SELECT setval(pg_get_serial_sequence('${table}','id'), ${max}, true)`)\n } else {\n // sqlite: sqlite_sequence only gets a row once a real AUTOINCREMENT\n // insert happens; explicit-id inserts bypass that, so upsert it.\n await tx.execRaw(\n `INSERT INTO sqlite_sequence (name, seq) SELECT '${table}', ${max} WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = '${table}')`,\n )\n await tx.execRaw(`UPDATE sqlite_sequence SET seq = ${max} WHERE name = '${table}'`)\n }\n }\n}\n\n/** Imports a full export (all 6 tables) into `destDriver`, preserving\n * original ids (required to keep foreign keys intact — see task #47\n * consultant advisory). The ENTIRE import (and, when `truncateFirst` is\n * set, the pre-import wipe) runs inside a single `destDriver.transaction()`\n * call: if anything fails partway, the whole operation rolls back and the\n * destination is left exactly as it was found.\n *\n * Callers MUST only mark the migration successful (e.g. call\n * `db.writeStorageState()`) AFTER this promise resolves without throwing —\n * never inside the transaction callback, never in a `finally`. */\nexport async function importFullExport(\n destDriver: DBDriver,\n data: FullExport,\n destDbType: 'sqlite' | 'postgres' | 'mysql',\n opts: { truncateFirst: boolean } = { truncateFirst: false },\n): Promise<void> {\n await destDriver.transaction(async (tx) => {\n if (opts.truncateFirst) {\n await truncateAllTables(tx)\n }\n\n for (const task of data.tasks) {\n await tx.exec(\n `INSERT INTO tasks (id, slug, title, description, status, assigned_to, created_at, started_at, completed_at, archived_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n task.id,\n task.slug,\n task.title,\n task.description,\n task.status,\n task.assigned_to,\n task.created_at,\n task.started_at,\n task.completed_at,\n task.archived_at,\n task.updated_at,\n ],\n )\n }\n\n for (const ta of data.taskAcceptance) {\n await tx.exec(\n `INSERT INTO task_acceptance (id, task_id, criterion, met) VALUES (?, ?, ?, ?)`,\n [ta.id, ta.task_id, ta.criterion, ta.met],\n )\n }\n\n for (const action of data.actions) {\n await tx.exec(\n `INSERT INTO actions (id, task_id, agent, status, created_at, completed_at, summary) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [action.id, action.task_id, action.agent, action.status, action.created_at, action.completed_at, action.summary],\n )\n }\n\n for (const section of data.sections) {\n await tx.exec(\n `INSERT INTO action_sections (id, action_id, section_type, content, created_at) VALUES (?, ?, ?, ?, ?)`,\n [section.id, section.action_id, section.section_type, section.content, section.created_at],\n )\n }\n\n for (const file of data.actionFiles) {\n await tx.exec(\n `INSERT INTO action_files (id, action_id, file_path, operation, notes) VALUES (?, ?, ?, ?, ?)`,\n [file.id, file.action_id, file.file_path, file.operation, file.notes],\n )\n }\n\n for (const tool of data.actionTools) {\n await tx.exec(\n `INSERT INTO action_tools (id, action_id, tool_name, args_json, result_summary, called_at) VALUES (?, ?, ?, ?, ?, ?)`,\n [tool.id, tool.action_id, tool.tool_name, tool.args_json, tool.result_summary, tool.called_at],\n )\n }\n\n await resetAutoincrementSequences(tx, destDbType)\n })\n}\n\n/** Resolves the physical sqlite file path for a given scope, sharing the\n * exact convention `openDB()` uses — extracted so `ahk migrate storage` can\n * locate the OLD file (by scope) without duplicating path logic. Does not\n * create any directories or files. */\nexport function resolveSqlitePathForScope(\n scope: 'local' | 'global',\n sqlitePath: string,\n cwd: string,\n config: HarnessConfig,\n homeDir: string,\n): string {\n return scope === 'global'\n ? join(resolveGlobalStorageDir(config, homeDir), 'harness.db')\n : resolve(cwd, sqlitePath)\n}\n\n/** Resolves the physical sqlite file path for `config`'s OWN current scope\n * (as opposed to `resolveSqlitePathForScope`, which resolves an arbitrary\n * scope — used by `ahk migrate storage` to probe both candidates). This is\n * the single mandatory entry point every call site should use instead of\n * reading `config.database`/`config.storage.sqlitePath` directly — routing\n * everything through here is what keeps new call sites from re-introducing\n * the \"reads a local-only field while scope=global\" bug class (task #55/#56). */\nexport function resolveSqlitePath(config: HarnessConfig, cwd: string, homeDir: string = homedir()): string {\n const sqlitePath = config.storage.scope === 'local' ? (config.storage.sqlitePath ?? DEFAULT_SQLITE_PATH) : DEFAULT_SQLITE_PATH\n return resolveSqlitePathForScope(config.storage.scope, sqlitePath, cwd, config, homeDir)\n}\n\n/** Resolves the physical current.md fallback path for `config`'s OWN current\n * scope, sharing the exact convention `HarnessDB.regenerateCurrentMd()`\n * uses. Mirrors `resolveSqlitePath()` — call sites (materializers, reset,\n * health) should use this instead of reading `storage.markdownFallback.path`\n * directly, since that field doesn't exist at all under scope='global'. */\nexport function resolveMarkdownFallbackPath(config: HarnessConfig, cwd: string, homeDir: string = homedir()): string {\n return config.storage.scope === 'global'\n ? join(resolveGlobalStorageDir(config, homeDir), 'current.md')\n : resolve(cwd, config.storage.markdownFallback.path)\n}\n\n/** Writes `<storageDir>/storage-state.json` under `cwd`. Standalone (not tied\n * to a live HarnessDB instance) so it can be called during init before a DB\n * connection exists, and reused by future migration tooling. */\nexport function writeStorageStateFile(cwd: string, storageDir: string, state: StorageState): void {\n const path = join(resolve(cwd), storageDir, 'storage-state.json')\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, JSON.stringify(state, null, 2) + '\\n', 'utf8')\n}\n\n/** Reads `<storageDir>/storage-state.json` under `cwd`. Returns `null` if it\n * doesn't exist or is malformed. Used by future migration tooling (#47) to\n * determine the real current storage state before migrating. */\nexport function readStorageStateFile(cwd: string, storageDir: string): StorageState | null {\n try {\n const path = join(resolve(cwd), storageDir, 'storage-state.json')\n if (!existsSync(path)) return null\n return JSON.parse(readFileSync(path, 'utf8')) as StorageState\n } catch {\n return null\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport async function openDB(config: HarnessConfig, cwd: string, homeDir: string = homedir()): Promise<HarnessDB> {\n const dbConfig = config.database\n let driver: DBDriver\n\n if (dbConfig.type === 'postgres') {\n const { PostgresDriver } = await import('./drivers/postgres')\n driver = new PostgresDriver(dbConfig)\n } else if (dbConfig.type === 'mysql') {\n const { MySQLDriver } = await import('./drivers/mysql')\n driver = new MySQLDriver(dbConfig)\n } else {\n const { SQLiteDriver } = await import('./drivers/sqlite')\n if (dbConfig.type !== 'sqlite') {\n throw new Error('Invalid database type')\n }\n\n let dbPath: string\n if (config.storage.scope === 'global') {\n const globalDir = resolveGlobalStorageDir(config, homeDir)\n // Defensive check: a UUID collision is negligible, but if the target\n // dir already exists with a DIFFERENT project's state, don't silently\n // reuse it — surface the conflict instead of assuming it's free.\n const existingStatePath = join(globalDir, 'storage-state.json')\n if (existsSync(existingStatePath)) {\n try {\n const existingState = JSON.parse(readFileSync(existingStatePath, 'utf8')) as StorageState\n if (existingState.projectId !== config.storage.projectId) {\n throw new Error(\n `Global storage dir ${globalDir} already holds a different project (projectId: ${existingState.projectId}). Refusing to reuse it.`,\n )\n }\n } catch (err) {\n if (err instanceof Error && err.message.includes('already holds a different project')) throw err\n // Malformed/unreadable state file — ignore and proceed, mkdirSync below is idempotent.\n }\n }\n mkdirSync(globalDir, { recursive: true })\n dbPath = join(globalDir, 'harness.db')\n } else {\n dbPath = resolve(cwd, config.storage.sqlitePath ?? DEFAULT_SQLITE_PATH)\n }\n\n driver = new SQLiteDriver(dbPath)\n }\n\n await driver.ensureSchema()\n return new HarnessDB(driver, config, homeDir)\n}\n","import type { DBDriver } from '../drivers/types'\nimport type { ActionFileRow, ActionRow, ActionSectionRow, AgentName } from '@/types'\n\nexport interface ActionWithDetails extends ActionRow {\n sections: ActionSectionRow[]\n files: ActionFileRow[]\n tools: ActionToolRow[]\n}\n\nexport interface ActionToolRow {\n id: number\n action_id: string\n tool_name: string\n args_json: string | null\n result_summary: string | null\n called_at: string\n}\n\nexport class ActionRepository {\n constructor(private driver: DBDriver) {}\n\n async create(id: string, taskId: number, agent: AgentName, now: string): Promise<void> {\n await this.driver.exec(\n `INSERT INTO actions (id, task_id, agent, status, created_at) VALUES (?, ?, ?, 'in_progress', ?)`,\n [id, taskId, agent, now],\n )\n }\n\n async complete(actionId: string, summary: string, now: string): Promise<void> {\n await this.driver.exec(\n `UPDATE actions SET status = 'completed', completed_at = ?, summary = ? WHERE id = ?`,\n [now, summary, actionId],\n )\n }\n\n async closeOrphaned(taskId: number, now: string): Promise<number> {\n return this.driver.exec(\n `UPDATE actions SET status = 'completed', completed_at = ?, summary = 'Auto-closed: task marked done' WHERE task_id = ? AND status = 'in_progress'`,\n [now, taskId],\n )\n }\n\n async getById(actionId: string): Promise<ActionRow | null> {\n return this.driver.queryOne<ActionRow>(`SELECT * FROM actions WHERE id = ?`, [actionId])\n }\n\n async getForTask(taskId: number): Promise<ActionRow[]> {\n return this.driver.query<ActionRow>(\n `SELECT * FROM actions WHERE task_id = ? ORDER BY created_at`,\n [taskId],\n )\n }\n\n async getAll(): Promise<ActionRow[]> {\n return this.driver.query<ActionRow>(`SELECT * FROM actions ORDER BY created_at`)\n }\n\n async getWithDetails(taskId: number): Promise<ActionWithDetails[]> {\n const actions = await this.getForTask(taskId)\n return Promise.all(\n actions.map(async (action) => ({\n ...action,\n sections: await this.getSections(action.id),\n files: await this.getFiles(action.id),\n tools: await this.getTools(action.id),\n })),\n )\n }\n\n // ─── Sections ─────────────────────────────────────────────────────────────\n\n async addSection(actionId: string, sectionType: string, content: string, now: string): Promise<void> {\n await this.driver.exec(\n `INSERT INTO action_sections (action_id, section_type, content, created_at) VALUES (?, ?, ?, ?)`,\n [actionId, sectionType, content, now],\n )\n }\n\n async getSections(actionId: string): Promise<ActionSectionRow[]> {\n return this.driver.query<ActionSectionRow>(\n `SELECT * FROM action_sections WHERE action_id = ? ORDER BY created_at`,\n [actionId],\n )\n }\n\n async getAllSections(): Promise<ActionSectionRow[]> {\n return this.driver.query<ActionSectionRow>(`SELECT * FROM action_sections ORDER BY created_at`)\n }\n\n // ─── Files ────────────────────────────────────────────────────────────────\n\n async addFile(\n actionId: string,\n filePath: string,\n operation: ActionFileRow['operation'],\n notes: string | null,\n ): Promise<void> {\n await this.driver.exec(\n `INSERT INTO action_files (action_id, file_path, operation, notes) VALUES (?, ?, ?, ?)`,\n [actionId, filePath, operation, notes],\n )\n }\n\n async getFiles(actionId: string): Promise<ActionFileRow[]> {\n return this.driver.query<ActionFileRow>(\n `SELECT * FROM action_files WHERE action_id = ?`,\n [actionId],\n )\n }\n\n async getFilesForTask(taskId: number): Promise<(ActionFileRow & { agent: AgentName })[]> {\n return this.driver.query<ActionFileRow & { agent: AgentName }>(\n `SELECT af.*, a.agent FROM action_files af JOIN actions a ON af.action_id = a.id WHERE a.task_id = ? ORDER BY a.agent, af.operation`,\n [taskId],\n )\n }\n\n /** Returns ALL action_files rows regardless of action — used by full DB\n * exports (e.g. `ahk migrate storage`) so file-touch records aren't lost. */\n async getAllFiles(): Promise<ActionFileRow[]> {\n return this.driver.query<ActionFileRow>(`SELECT * FROM action_files ORDER BY id`)\n }\n\n // ─── Tools ────────────────────────────────────────────────────────────────\n\n async addTool(\n actionId: string,\n toolName: string,\n argsJson: string | null,\n resultSummary: string | null,\n now: string,\n ): Promise<void> {\n await this.driver.exec(\n `INSERT INTO action_tools (action_id, tool_name, args_json, result_summary, called_at) VALUES (?, ?, ?, ?, ?)`,\n [actionId, toolName, argsJson, resultSummary, now],\n )\n }\n\n async getTools(actionId: string): Promise<ActionToolRow[]> {\n return this.driver.query<ActionToolRow>(\n `SELECT * FROM action_tools WHERE action_id = ? ORDER BY called_at`,\n [actionId],\n )\n }\n\n /** Returns ALL action_tools rows regardless of action — used by full DB\n * exports (e.g. `ahk migrate storage`) so tool-call records aren't lost. */\n async getAllTools(): Promise<ActionToolRow[]> {\n return this.driver.query<ActionToolRow>(`SELECT * FROM action_tools ORDER BY id`)\n }\n\n async getTopTools(limit: number): Promise<{ tool_name: string; uses: number }[]> {\n return this.driver.query<{ tool_name: string; uses: number }>(\n `SELECT tool_name, COUNT(*) as uses FROM action_tools GROUP BY tool_name ORDER BY uses DESC LIMIT ?`,\n [limit],\n )\n }\n}\n","import type { DBDriver } from '../drivers/types'\nimport type {\n AgentStatRow,\n CountRow,\n RecentFileRow,\n RecentToolRow,\n TimelineRow,\n TopFileRow,\n} from '../server-types'\n\nexport interface DBCounts {\n totalActions: number\n totalFiles: number\n uniqueTools: number\n activeAgents: number\n}\n\nexport { AgentStatRow, RecentFileRow, RecentToolRow, TimelineRow, TopFileRow }\n\nconst AGENT_ORDER = ['lead', 'explorer', 'builder', 'reviewer']\n\nexport class StatsRepository {\n constructor(private driver: DBDriver) { }\n\n async getCounts(): Promise<DBCounts> {\n const [{ total: totalActions }] = await this.driver.query<CountRow>(\n `SELECT COUNT(*) as total FROM actions`,\n )\n const [{ total: totalFiles }] = await this.driver.query<CountRow>(\n `SELECT COUNT(*) as total FROM action_files`,\n )\n const [{ total: uniqueTools }] = await this.driver.query<CountRow>(\n `SELECT COUNT(DISTINCT tool_name) as total FROM action_tools`,\n )\n const [{ total: activeAgents }] = await this.driver.query<CountRow>(\n `SELECT COUNT(DISTINCT agent) as total FROM actions WHERE status = 'in_progress'`,\n )\n return { totalActions, totalFiles, uniqueTools, activeAgents }\n }\n\n async getRecentTools(limit: number): Promise<RecentToolRow[]> {\n return this.driver.query<RecentToolRow>(\n `SELECT at.*, t.id as task_id, t.title as task_title, t.slug as task_slug, a.agent\n FROM action_tools at\n JOIN actions a ON at.action_id = a.id\n JOIN tasks t ON a.task_id = t.id\n ORDER BY at.called_at DESC\n LIMIT ?`,\n [limit],\n )\n }\n\n async getTopFiles(limit: number): Promise<TopFileRow[]> {\n return this.driver.query<TopFileRow>(\n `SELECT\n file_path,\n COUNT(*) as total,\n SUM(CASE WHEN operation='read' THEN 1 ELSE 0 END) as read,\n SUM(CASE WHEN operation='created' THEN 1 ELSE 0 END) as created,\n SUM(CASE WHEN operation='modified' THEN 1 ELSE 0 END) as modified,\n SUM(CASE WHEN operation='deleted' THEN 1 ELSE 0 END) as deleted\n FROM action_files\n GROUP BY file_path\n ORDER BY total DESC\n LIMIT ?`,\n [limit],\n )\n }\n\n async getRecentFiles(limit: number): Promise<RecentFileRow[]> {\n return this.driver.query<RecentFileRow>(\n `SELECT af.*, t.id as task_id, t.title as task_title, t.slug as task_slug,\n a.agent, a.created_at as called_at\n FROM action_files af\n JOIN actions a ON af.action_id = a.id\n JOIN tasks t ON a.task_id = t.id\n ORDER BY a.created_at DESC\n LIMIT ?`,\n [limit],\n )\n }\n\n async getAgentStats(): Promise<AgentStatRow[]> {\n const rows = await this.driver.query<AgentStatRow>(\n `SELECT\n a.agent,\n COUNT(*) as actions_total,\n SUM(CASE WHEN a.status='completed' THEN 1 ELSE 0 END) as actions_done,\n SUM(CASE WHEN a.status='blocked' THEN 1 ELSE 0 END) as actions_blocked,\n COUNT(DISTINCT a.task_id) as tasks_worked,\n COUNT(DISTINCT af.file_path) as files_touched\n FROM actions a\n LEFT JOIN action_files af ON af.action_id = a.id\n GROUP BY a.agent\n ORDER BY actions_total DESC`,\n )\n return rows.sort((a, b) => {\n const ai = AGENT_ORDER.indexOf(a.agent)\n const bi = AGENT_ORDER.indexOf(b.agent)\n if (ai === -1 && bi === -1) return 0\n if (ai === -1) return 1\n if (bi === -1) return -1\n return ai - bi\n })\n }\n\n async getTimeline(limit: number): Promise<TimelineRow[]> {\n return this.driver.query<TimelineRow>(\n `SELECT a.*, t.title as task_title, t.slug as task_slug, t.status as task_status\n FROM actions a\n JOIN tasks t ON a.task_id = t.id\n ORDER BY a.created_at DESC\n LIMIT ?`,\n [limit],\n )\n }\n}\n","import type { DBDriver } from '../drivers/types'\nimport type { TaskAcceptanceRow, TaskRow, TaskStatus } from '@/types'\n\nexport interface TaskWithAcceptance extends TaskRow {\n acceptance_total: number\n acceptance_met: number\n}\n\nexport class TaskRepository {\n constructor(private driver: DBDriver) {}\n\n async add(params: {\n slug: string\n title: string\n description?: string | null\n status?: TaskStatus\n }): Promise<number> {\n const now = new Date().toISOString()\n return this.driver.insert(\n `INSERT INTO tasks (slug, title, description, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,\n [params.slug, params.title, params.description ?? null, params.status ?? 'pending', now, now],\n )\n }\n\n async addAcceptance(taskId: number, criteria: string[]): Promise<void> {\n for (const criterion of criteria) {\n await this.driver.exec(\n `INSERT INTO task_acceptance (task_id, criterion) VALUES (?, ?)`,\n [taskId, criterion],\n )\n }\n }\n\n async getAll(status?: TaskStatus, includeArchived = false): Promise<TaskRow[]> {\n let sql = `SELECT * FROM tasks`\n const params: unknown[] = []\n const conditions: string[] = []\n\n if (!includeArchived) {\n conditions.push(`archived_at IS NULL`)\n }\n if (status) {\n conditions.push(`status = ?`)\n params.push(status)\n }\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`\n }\n sql += ` ORDER BY CASE status WHEN 'pending' THEN 1 WHEN 'in_progress' THEN 2 WHEN 'blocked' THEN 3 WHEN 'done' THEN 4 ELSE 5 END, updated_at DESC`\n\n return this.driver.query<TaskRow>(sql, params)\n }\n\n async getAllWithAcceptanceCounts(includeArchived = false): Promise<TaskWithAcceptance[]> {\n let sql = `\n SELECT t.*,\n COUNT(ta.id) as acceptance_total,\n COALESCE(SUM(ta.met), 0) as acceptance_met\n FROM tasks t\n LEFT JOIN task_acceptance ta ON ta.task_id = t.id\n `\n if (!includeArchived) {\n sql += ` WHERE t.archived_at IS NULL`\n }\n sql += ` GROUP BY t.id ORDER BY CASE t.status WHEN 'pending' THEN 1 WHEN 'in_progress' THEN 2 WHEN 'blocked' THEN 3 WHEN 'done' THEN 4 ELSE 5 END, t.updated_at DESC`\n return this.driver.query<TaskWithAcceptance>(sql)\n }\n\n async getById(id: number): Promise<TaskRow | null> {\n return this.driver.queryOne<TaskRow>(`SELECT * FROM tasks WHERE id = ?`, [id])\n }\n\n async getBySlug(slug: string): Promise<TaskRow | null> {\n return this.driver.queryOne<TaskRow>(`SELECT * FROM tasks WHERE slug = ?`, [slug])\n }\n\n async getAcceptance(taskId: number): Promise<TaskAcceptanceRow[]> {\n return this.driver.query<TaskAcceptanceRow>(\n `SELECT * FROM task_acceptance WHERE task_id = ?`,\n [taskId],\n )\n }\n\n /** Returns ALL task_acceptance rows regardless of task — used by full DB\n * exports (e.g. `ahk migrate storage`) so criteria aren't silently dropped. */\n async getAllAcceptance(): Promise<TaskAcceptanceRow[]> {\n return this.driver.query<TaskAcceptanceRow>(`SELECT * FROM task_acceptance ORDER BY id`)\n }\n\n async setStatus(id: number, status: TaskStatus, extra?: { started_at?: string; completed_at?: string }): Promise<void> {\n const now = new Date().toISOString()\n if (extra?.started_at) {\n await this.driver.exec(\n `UPDATE tasks SET status = ?, started_at = ?, updated_at = ? WHERE id = ?`,\n [status, extra.started_at, now, id],\n )\n } else if (extra?.completed_at) {\n await this.driver.exec(\n `UPDATE tasks SET status = ?, completed_at = ?, updated_at = ? WHERE id = ?`,\n [status, extra.completed_at, now, id],\n )\n } else {\n await this.driver.exec(`UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?`, [status, now, id])\n }\n }\n\n async update(id: number, params: { title?: string; description?: string | null; slug?: string }): Promise<void> {\n const sets: string[] = []\n const vals: unknown[] = []\n const now = new Date().toISOString()\n if (params.title !== undefined) { sets.push('title = ?'); vals.push(params.title) }\n if (params.description !== undefined) { sets.push('description = ?'); vals.push(params.description) }\n if (params.slug !== undefined) { sets.push('slug = ?'); vals.push(params.slug) }\n if (sets.length === 0) return\n sets.push('updated_at = ?')\n vals.push(now)\n vals.push(id)\n await this.driver.exec(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`, vals)\n }\n\n async replaceAcceptance(taskId: number, criteria: string[]): Promise<void> {\n await this.driver.exec(`DELETE FROM task_acceptance WHERE task_id = ?`, [taskId])\n for (const criterion of criteria) {\n await this.driver.exec(\n `INSERT INTO task_acceptance (task_id, criterion) VALUES (?, ?)`,\n [taskId, criterion],\n )\n }\n }\n\n async archive(id: number): Promise<void> {\n const now = new Date().toISOString()\n await this.driver.exec(`UPDATE tasks SET archived_at = ?, updated_at = ? WHERE id = ?`, [now, now, id])\n }\n\n async unarchive(id: number): Promise<void> {\n const now = new Date().toISOString()\n await this.driver.exec(`UPDATE tasks SET archived_at = NULL, updated_at = ? WHERE id = ?`, [now, id])\n }\n\n async getArchived(): Promise<TaskRow[]> {\n return this.driver.query<TaskRow>(\n `SELECT * FROM tasks WHERE archived_at IS NOT NULL ORDER BY archived_at DESC`\n )\n }\n\n async claim(id: number, agent: string, now: string): Promise<number> {\n return this.driver.exec(\n `UPDATE tasks SET status = 'in_progress', assigned_to = ?, started_at = ?, updated_at = ? WHERE id = ? AND status = 'pending'`,\n [agent, now, now, id],\n )\n }\n\n async markAcceptanceMet(criterionId: number): Promise<void> {\n await this.driver.exec(`UPDATE task_acceptance SET met = 1 WHERE id = ?`, [criterionId])\n }\n\n async getStatusSummary(): Promise<{ status: string; total: number }[]> {\n return this.driver.query<{ status: string; total: number }>(\n `SELECT status, COUNT(*) as total FROM tasks WHERE archived_at IS NULL GROUP BY status`,\n )\n }\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,eAAe;AACxB,SAAS,SAAS,MAAM,eAAe;;;ACehC,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAAoB,QAAkB;AAAlB;AAAA,EAAmB;AAAA,EAAnB;AAAA,EAEpB,MAAM,OAAO,IAAY,QAAgB,OAAkB,KAA4B;AACrF,UAAM,KAAK,OAAO;AAAA,MAChB;AAAA,MACA,CAAC,IAAI,QAAQ,OAAO,GAAG;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,UAAkB,SAAiB,KAA4B;AAC5E,UAAM,KAAK,OAAO;AAAA,MAChB;AAAA,MACA,CAAC,KAAK,SAAS,QAAQ;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,QAAgB,KAA8B;AAChE,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,KAAK,MAAM;AAAA,IACd;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,UAA6C;AACzD,WAAO,KAAK,OAAO,SAAoB,sCAAsC,CAAC,QAAQ,CAAC;AAAA,EACzF;AAAA,EAEA,MAAM,WAAW,QAAsC;AACrD,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,MAAM;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,SAA+B;AACnC,WAAO,KAAK,OAAO,MAAiB,2CAA2C;AAAA,EACjF;AAAA,EAEA,MAAM,eAAe,QAA8C;AACjE,UAAM,UAAU,MAAM,KAAK,WAAW,MAAM;AAC5C,WAAO,QAAQ;AAAA,MACb,QAAQ,IAAI,OAAO,YAAY;AAAA,QAC7B,GAAG;AAAA,QACH,UAAU,MAAM,KAAK,YAAY,OAAO,EAAE;AAAA,QAC1C,OAAO,MAAM,KAAK,SAAS,OAAO,EAAE;AAAA,QACpC,OAAO,MAAM,KAAK,SAAS,OAAO,EAAE;AAAA,MACtC,EAAE;AAAA,IACJ;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,WAAW,UAAkB,aAAqB,SAAiB,KAA4B;AACnG,UAAM,KAAK,OAAO;AAAA,MAChB;AAAA,MACA,CAAC,UAAU,aAAa,SAAS,GAAG;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,UAA+C;AAC/D,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAM,iBAA8C;AAClD,WAAO,KAAK,OAAO,MAAwB,mDAAmD;AAAA,EAChG;AAAA;AAAA,EAIA,MAAM,QACJ,UACA,UACA,WACA,OACe;AACf,UAAM,KAAK,OAAO;AAAA,MAChB;AAAA,MACA,CAAC,UAAU,UAAU,WAAW,KAAK;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,UAA4C;AACzD,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB,QAAmE;AACvF,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,MAAM;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAM,cAAwC;AAC5C,WAAO,KAAK,OAAO,MAAqB,wCAAwC;AAAA,EAClF;AAAA;AAAA,EAIA,MAAM,QACJ,UACA,UACA,UACA,eACA,KACe;AACf,UAAM,KAAK,OAAO;AAAA,MAChB;AAAA,MACA,CAAC,UAAU,UAAU,UAAU,eAAe,GAAG;AAAA,IACnD;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,UAA4C;AACzD,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,QAAQ;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAM,cAAwC;AAC5C,WAAO,KAAK,OAAO,MAAqB,wCAAwC;AAAA,EAClF;AAAA,EAEA,MAAM,YAAY,OAA+D;AAC/E,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,KAAK;AAAA,IACR;AAAA,EACF;AACF;;;AC1IA,IAAM,cAAc,CAAC,QAAQ,YAAY,WAAW,UAAU;AAEvD,IAAM,kBAAN,MAAsB;AAAA,EAC3B,YAAoB,QAAkB;AAAlB;AAAA,EAAoB;AAAA,EAApB;AAAA,EAEpB,MAAM,YAA+B;AACnC,UAAM,CAAC,EAAE,OAAO,aAAa,CAAC,IAAI,MAAM,KAAK,OAAO;AAAA,MAClD;AAAA,IACF;AACA,UAAM,CAAC,EAAE,OAAO,WAAW,CAAC,IAAI,MAAM,KAAK,OAAO;AAAA,MAChD;AAAA,IACF;AACA,UAAM,CAAC,EAAE,OAAO,YAAY,CAAC,IAAI,MAAM,KAAK,OAAO;AAAA,MACjD;AAAA,IACF;AACA,UAAM,CAAC,EAAE,OAAO,aAAa,CAAC,IAAI,MAAM,KAAK,OAAO;AAAA,MAClD;AAAA,IACF;AACA,WAAO,EAAE,cAAc,YAAY,aAAa,aAAa;AAAA,EAC/D;AAAA,EAEA,MAAM,eAAe,OAAyC;AAC5D,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,CAAC,KAAK;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAAsC;AACtD,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWA,CAAC,KAAK;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,OAAyC;AAC5D,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,CAAC,KAAK;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,gBAAyC;AAC7C,UAAM,OAAO,MAAM,KAAK,OAAO;AAAA,MAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWF;AACA,WAAO,KAAK,KAAK,CAAC,GAAG,MAAM;AACzB,YAAM,KAAK,YAAY,QAAQ,EAAE,KAAK;AACtC,YAAM,KAAK,YAAY,QAAQ,EAAE,KAAK;AACtC,UAAI,OAAO,MAAM,OAAO,GAAI,QAAO;AACnC,UAAI,OAAO,GAAI,QAAO;AACtB,UAAI,OAAO,GAAI,QAAO;AACtB,aAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAAY,OAAuC;AACvD,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,CAAC,KAAK;AAAA,IACR;AAAA,EACF;AACF;;;AC5GO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAAoB,QAAkB;AAAlB;AAAA,EAAmB;AAAA,EAAnB;AAAA,EAEpB,MAAM,IAAI,QAKU;AAClB,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,OAAO,MAAM,OAAO,OAAO,OAAO,eAAe,MAAM,OAAO,UAAU,WAAW,KAAK,GAAG;AAAA,IAC9F;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,QAAgB,UAAmC;AACrE,eAAW,aAAa,UAAU;AAChC,YAAM,KAAK,OAAO;AAAA,QAChB;AAAA,QACA,CAAC,QAAQ,SAAS;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,QAAqB,kBAAkB,OAA2B;AAC7E,QAAI,MAAM;AACV,UAAM,SAAoB,CAAC;AAC3B,UAAM,aAAuB,CAAC;AAE9B,QAAI,CAAC,iBAAiB;AACpB,iBAAW,KAAK,qBAAqB;AAAA,IACvC;AACA,QAAI,QAAQ;AACV,iBAAW,KAAK,YAAY;AAC5B,aAAO,KAAK,MAAM;AAAA,IACpB;AACA,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO,UAAU,WAAW,KAAK,OAAO,CAAC;AAAA,IAC3C;AACA,WAAO;AAEP,WAAO,KAAK,OAAO,MAAe,KAAK,MAAM;AAAA,EAC/C;AAAA,EAEA,MAAM,2BAA2B,kBAAkB,OAAsC;AACvF,QAAI,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOV,QAAI,CAAC,iBAAiB;AACpB,aAAO;AAAA,IACT;AACA,WAAO;AACP,WAAO,KAAK,OAAO,MAA0B,GAAG;AAAA,EAClD;AAAA,EAEA,MAAM,QAAQ,IAAqC;AACjD,WAAO,KAAK,OAAO,SAAkB,oCAAoC,CAAC,EAAE,CAAC;AAAA,EAC/E;AAAA,EAEA,MAAM,UAAU,MAAuC;AACrD,WAAO,KAAK,OAAO,SAAkB,sCAAsC,CAAC,IAAI,CAAC;AAAA,EACnF;AAAA,EAEA,MAAM,cAAc,QAA8C;AAChE,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,MAAM;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAM,mBAAiD;AACrD,WAAO,KAAK,OAAO,MAAyB,2CAA2C;AAAA,EACzF;AAAA,EAEA,MAAM,UAAU,IAAY,QAAoB,OAAuE;AACrH,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAI,OAAO,YAAY;AACrB,YAAM,KAAK,OAAO;AAAA,QAChB;AAAA,QACA,CAAC,QAAQ,MAAM,YAAY,KAAK,EAAE;AAAA,MACpC;AAAA,IACF,WAAW,OAAO,cAAc;AAC9B,YAAM,KAAK,OAAO;AAAA,QAChB;AAAA,QACA,CAAC,QAAQ,MAAM,cAAc,KAAK,EAAE;AAAA,MACtC;AAAA,IACF,OAAO;AACL,YAAM,KAAK,OAAO,KAAK,4DAA4D,CAAC,QAAQ,KAAK,EAAE,CAAC;AAAA,IACtG;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,IAAY,QAAuF;AAC9G,UAAM,OAAiB,CAAC;AACxB,UAAM,OAAkB,CAAC;AACzB,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAI,OAAO,UAAU,QAAW;AAAE,WAAK,KAAK,WAAW;AAAG,WAAK,KAAK,OAAO,KAAK;AAAA,IAAE;AAClF,QAAI,OAAO,gBAAgB,QAAW;AAAE,WAAK,KAAK,iBAAiB;AAAG,WAAK,KAAK,OAAO,WAAW;AAAA,IAAE;AACpG,QAAI,OAAO,SAAS,QAAW;AAAE,WAAK,KAAK,UAAU;AAAG,WAAK,KAAK,OAAO,IAAI;AAAA,IAAE;AAC/E,QAAI,KAAK,WAAW,EAAG;AACvB,SAAK,KAAK,gBAAgB;AAC1B,SAAK,KAAK,GAAG;AACb,SAAK,KAAK,EAAE;AACZ,UAAM,KAAK,OAAO,KAAK,oBAAoB,KAAK,KAAK,IAAI,CAAC,iBAAiB,IAAI;AAAA,EACjF;AAAA,EAEA,MAAM,kBAAkB,QAAgB,UAAmC;AACzE,UAAM,KAAK,OAAO,KAAK,iDAAiD,CAAC,MAAM,CAAC;AAChF,eAAW,aAAa,UAAU;AAChC,YAAM,KAAK,OAAO;AAAA,QAChB;AAAA,QACA,CAAC,QAAQ,SAAS;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,IAA2B;AACvC,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,KAAK,OAAO,KAAK,iEAAiE,CAAC,KAAK,KAAK,EAAE,CAAC;AAAA,EACxG;AAAA,EAEA,MAAM,UAAU,IAA2B;AACzC,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,KAAK,OAAO,KAAK,oEAAoE,CAAC,KAAK,EAAE,CAAC;AAAA,EACtG;AAAA,EAEA,MAAM,cAAkC;AACtC,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,IAAY,OAAe,KAA8B;AACnE,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,MACA,CAAC,OAAO,KAAK,KAAK,EAAE;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,aAAoC;AAC1D,UAAM,KAAK,OAAO,KAAK,mDAAmD,CAAC,WAAW,CAAC;AAAA,EACzF;AAAA,EAEA,MAAM,mBAAiE;AACrE,WAAO,KAAK,OAAO;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;;;AH3HA,IAAM,uBAAuB,CAAC,SAAS,mBAAmB,mBAAmB,gBAAgB,cAAc;AAK3G,IAAM,qBAAqB,CAAC,SAAS,mBAAmB,WAAW,mBAAmB,gBAAgB,cAAc;AAIpH,IAAM,qBAAqB,CAAC,GAAG,kBAAkB,EAAE,QAAQ;AASpD,IAAM,sBAAsB;AAQ5B,IAAM,wBAAwB;AAK9B,SAAS,wBAAwB,QAAuB,UAAkB,QAAQ,GAAW;AAClG,SAAO,KAAK,SAAS,YAAY,OAAO,OAAO,QAAQ,SAAS;AAClE;AAIO,IAAM,YAAN,MAAgB;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACD;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EAER,YAAY,QAAkB,QAAuB,UAAkB,QAAQ,GAAG;AAChF,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,UAAU;AACf,SAAK,QAAQ,IAAI,eAAe,MAAM;AACtC,SAAK,UAAU,IAAI,iBAAiB,MAAM;AAC1C,SAAK,QAAQ,IAAI,gBAAgB,MAAM;AAAA,EACzC;AAAA;AAAA,EAIA,MAAM,QAAQ,QAKO;AACnB,UAAM,SAAS,MAAM,KAAK,MAAM,IAAI;AAAA,MAClC,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,IACtB,CAAC;AACD,QAAI,OAAO,YAAY,QAAQ;AAC7B,YAAM,KAAK,MAAM,cAAc,QAAQ,OAAO,UAAU;AAAA,IAC1D;AACA,UAAM,KAAK,oBAAoB;AAC/B,WAAQ,MAAM,KAAK,MAAM,QAAQ,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,SAAS,QAAqB,kBAAkB,OAA2B;AAC/E,WAAO,KAAK,MAAM,OAAO,QAAQ,eAAe;AAAA,EAClD;AAAA,EAEA,MAAM,YAAY,IAAqC;AACrD,WAAO,KAAK,MAAM,QAAQ,EAAE;AAAA,EAC9B;AAAA,EAEA,MAAM,cAAc,MAAuC;AACzD,WAAO,KAAK,MAAM,UAAU,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,kBAAkB,QAA8C;AACpE,WAAO,KAAK,MAAM,cAAc,MAAM;AAAA,EACxC;AAAA,EAEA,MAAM,iBAAiB,UAA2B,QAAsC;AACtF,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,OACJ,OAAO,aAAa,WAChB,MAAM,KAAK,MAAM,QAAQ,QAAQ,IACjC,MAAM,KAAK,MAAM,UAAU,QAAQ;AACzC,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAExD,QAAI,WAAW,iBAAiB,CAAC,KAAK,YAAY;AAChD,YAAM,KAAK,MAAM,UAAU,KAAK,IAAI,QAAQ,EAAE,YAAY,IAAI,CAAC;AAAA,IACjE,WAAW,WAAW,QAAQ;AAC5B,YAAM,KAAK,MAAM,UAAU,KAAK,IAAI,QAAQ,EAAE,cAAc,IAAI,CAAC;AAAA,IACnE,OAAO;AACL,YAAM,KAAK,MAAM,UAAU,KAAK,IAAI,MAAM;AAAA,IAC5C;AAEA,UAAM,KAAK,oBAAoB;AAC/B,WAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,EAAE;AAAA,EAC1C;AAAA,EAEA,MAAM,UAAU,IAAY,OAAwC;AAClE,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,WAAO,KAAK,OAAO,YAAY,OAAO,OAAO;AAE3C,YAAM,UAAU,IAAI,eAAe,EAAE;AACrC,YAAM,UAAU,MAAM,QAAQ,MAAM,IAAI,OAAO,GAAG;AAClD,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,OAAO,MAAM,QAAQ,QAAQ,EAAE;AACrC,UAAI,CAAC,QAAQ,KAAK,WAAW,iBAAiB,KAAK,gBAAgB,MAAO,QAAO;AACjF,YAAM,KAAK,oBAAoB;AAC/B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,kBAAkB,aAAoC;AAC1D,WAAO,KAAK,MAAM,kBAAkB,WAAW;AAAA,EACjD;AAAA,EAEA,MAAM,WAAW,IAAY,QAA0F;AACrH,UAAM,KAAK,MAAM,OAAO,IAAI,MAAM;AAClC,UAAM,KAAK,oBAAoB;AAC/B,WAAQ,MAAM,KAAK,MAAM,QAAQ,EAAE;AAAA,EACrC;AAAA,EAEA,MAAM,qBAAqB,QAAgB,UAAmC;AAC5E,UAAM,KAAK,MAAM,kBAAkB,QAAQ,QAAQ;AACnD,UAAM,KAAK,oBAAoB;AAAA,EACjC;AAAA,EAEA,MAAM,YAAY,IAA8B;AAC9C,UAAM,KAAK,MAAM,QAAQ,EAAE;AAC3B,UAAM,KAAK,oBAAoB;AAC/B,WAAQ,MAAM,KAAK,MAAM,QAAQ,EAAE;AAAA,EACrC;AAAA,EAEA,MAAM,cAAc,IAA8B;AAChD,UAAM,KAAK,MAAM,UAAU,EAAE;AAC7B,UAAM,KAAK,oBAAoB;AAC/B,WAAQ,MAAM,KAAK,MAAM,QAAQ,EAAE;AAAA,EACrC;AAAA,EAEA,MAAM,mBAAuC;AAC3C,WAAO,KAAK,MAAM,YAAY;AAAA,EAChC;AAAA,EAEA,MAAM,mBAAiE;AACrE,WAAO,KAAK,MAAM,iBAAiB;AAAA,EACrC;AAAA;AAAA,EAIA,MAAM,YAAY,QAAgB,OAAsC;AACtE,UAAM,KAAK,WAAW;AACtB,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,KAAK,QAAQ,OAAO,IAAI,QAAQ,OAAO,GAAG;AAChD,UAAM,KAAK,oBAAoB;AAC/B,WAAQ,MAAM,KAAK,QAAQ,QAAQ,EAAE;AAAA,EACvC;AAAA,EAEA,MAAM,aAAa,UAAkB,aAAqB,SAAgC;AACxF,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,KAAK,QAAQ,WAAW,UAAU,aAAa,SAAS,GAAG;AACjE,UAAM,KAAK,oBAAoB;AAAA,EACjC;AAAA,EAEA,MAAM,eAAe,UAAkB,SAAqC;AAC1E,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,KAAK,QAAQ,SAAS,UAAU,SAAS,GAAG;AAClD,UAAM,KAAK,oBAAoB;AAC/B,WAAQ,MAAM,KAAK,QAAQ,QAAQ,QAAQ;AAAA,EAC7C;AAAA,EAEA,MAAM,qBAAqB,QAAiC;AAC1D,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,WAAO,KAAK,QAAQ,cAAc,QAAQ,GAAG;AAAA,EAC/C;AAAA,EAEA,MAAM,UAAU,UAA6C;AAC3D,WAAO,KAAK,QAAQ,QAAQ,QAAQ;AAAA,EACtC;AAAA,EAEA,MAAM,kBAAkB,QAAsC;AAC5D,WAAO,KAAK,QAAQ,WAAW,MAAM;AAAA,EACvC;AAAA,EAEA,MAAM,kBAAkB,UAA+C;AACrE,WAAO,KAAK,QAAQ,YAAY,QAAQ;AAAA,EAC1C;AAAA,EAEA,MAAM,WACJ,UACA,UACA,WACA,OACe;AACf,WAAO,KAAK,QAAQ,QAAQ,UAAU,UAAU,WAAW,SAAS,IAAI;AAAA,EAC1E;AAAA,EAEA,MAAM,WACJ,UACA,UACA,UACA,eACe;AACf,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,WAAO,KAAK,QAAQ,QAAQ,UAAU,UAAU,YAAY,MAAM,iBAAiB,MAAM,GAAG;AAAA,EAC9F;AAAA,EAEA,MAAM,gBAAgB,QAAmE;AACvF,WAAO,KAAK,QAAQ,gBAAgB,MAAM;AAAA,EAC5C;AAAA,EAEA,MAAM,YAAY,QAAQ,IAAoD;AAC5E,WAAO,KAAK,QAAQ,YAAY,KAAK;AAAA,EACvC;AAAA;AAAA,EAIA,MAAM,sBAAqC;AACzC,QAAI,CAAC,KAAK,OAAO,QAAQ,iBAAiB,QAAS;AAEnD,UAAM,SACJ,KAAK,OAAO,QAAQ,UAAU,WAC1B,KAAK,wBAAwB,KAAK,QAAQ,KAAK,OAAO,GAAG,YAAY,IACrE,QAAQ,KAAK,OAAO,QAAQ,iBAAiB,IAAI;AACvD,cAAU,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAE9C,UAAM,aAAa,MAAM,KAAK,MAAM,OAAO,aAAa;AACxD,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,QAAI,KAAK;AAAA;AACT,UAAM,sBAAsB,GAAG;AAAA;AAAA;AAC/B,UAAM;AAAA;AAAA;AAEN,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM;AAAA;AAAA;AACN,YAAM,UAAU,MAAM,KAAK,MAAM,OAAO,SAAS;AACjD,UAAI,QAAQ,SAAS,GAAG;AACtB,cAAM;AAAA;AACN,mBAAW,KAAK,QAAQ,MAAM,GAAG,CAAC,GAAG;AACnC,gBAAM,QAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,IAAI;AAAA;AAAA,QAC9C;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,QAAQ,YAAY;AAC7B,cAAM;AAAA;AACN,cAAM,aAAa,KAAK,EAAE;AAAA;AAC1B,cAAM,eAAe,KAAK,IAAI;AAAA;AAC9B,cAAM,iBAAiB,KAAK,MAAM;AAAA;AAClC,cAAM,kBAAkB,KAAK,cAAc,SAAS;AAAA;AAAA;AAEpD,cAAM,cAAc,MAAM,KAAK,QAAQ,WAAW,KAAK,EAAE;AACzD,YAAI,YAAY,SAAS,GAAG;AAC1B,gBAAM;AAAA;AACN,gBAAM;AAAA;AACN,gBAAM;AAAA;AACN,qBAAW,KAAK,aAAa;AAC3B,kBAAM,UAAU,EAAE,WAAW,MAAM,IAAI,EAAE;AACzC,kBAAM,WAAW,EAAE,WAAW,IAAI,MAAM,GAAG,EAAE,EAAE,OAAO,EAAE;AACxD,kBAAM,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,MAAM,EAAE,OAAO,OAAO,EAAE,CAAC,MAAM,OAAO,MAAM,OAAO;AAAA;AAAA,UACjF;AACA,gBAAM;AAAA;AAAA,QACR;AAEA,cAAM,aAAa,MAAM,KAAK,MAAM,cAAc,KAAK,EAAE;AACzD,YAAI,WAAW,SAAS,GAAG;AACzB,gBAAM;AAAA;AACN,qBAAW,KAAK,YAAY;AAC1B,kBAAM,MAAM,EAAE,MAAM,MAAM,GAAG,KAAK,EAAE,SAAS;AAAA;AAAA,UAC/C;AACA,gBAAM;AAAA;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,kBAAc,QAAQ,IAAI,MAAM;AAAA,EAClC;AAAA;AAAA,EAIA,MAAM,SAAsC,QAAgB,QAAiC;AAC3F,WAAO,KAAK,OAAO,MAAS,KAAK,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAkC;AACtC,WAAO;AAAA,MACL,OAAO,MAAM,KAAK,MAAM,OAAO,QAAW,IAAI;AAAA,MAC9C,gBAAgB,MAAM,KAAK,MAAM,iBAAiB;AAAA,MAClD,SAAS,MAAM,KAAK,QAAQ,OAAO;AAAA,MACnC,UAAU,MAAM,KAAK,QAAQ,eAAe;AAAA,MAC5C,aAAa,MAAM,KAAK,QAAQ,YAAY;AAAA,MAC5C,aAAa,MAAM,KAAK,QAAQ,YAAY;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAA6E;AACjF,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,MAAkB,QAAyC,MAAkD;AAClI,WAAO,iBAAiB,KAAK,QAAQ,MAAM,QAAQ,IAAI;AAAA,EACzD;AAAA,EAEA,MAAM,YAA2B;AAC/B,UAAM,KAAK,OAAO,UAAU;AAAA,EAC9B;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B;AAAA;AAAA,EAIA,MAAM,oBACJ,OAC6C;AAC7C,QAAI,QAAQ;AACZ,QAAI,UAAU;AACd,eAAW,KAAK,OAAO;AACrB,UAAI,MAAM,KAAK,MAAM,UAAU,EAAE,IAAI,GAAG;AACtC;AACA;AAAA,MACF;AACA,YAAM,KAAK,QAAQ,CAAC;AACpB;AAAA,IACF;AACA,WAAO,EAAE,OAAO,QAAQ;AAAA,EAC1B;AAAA,EAEA,MAAM,iBAAiB,KAA4B;AACjD,UAAM,WAAW,MAAM,KAAK,MAAM,OAAO,QAAW,IAAI;AACxD,UAAM,OAAO,MAAM,QAAQ;AAAA,MACzB,SAAS,IAAI,OAAO,OAAO;AAAA,QACzB,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,QACT,aAAa,EAAE,eAAe;AAAA,QAC9B,aAAa,MAAM,KAAK,MAAM,cAAc,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,QACzE,QAAQ,EAAE;AAAA,MACZ,EAAE;AAAA,IACJ;AACA,UAAM,OAAO,KAAK,QAAQ,GAAG,GAAG,KAAK,OAAO,QAAQ,KAAK,mBAAmB;AAC5E,cAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,kBAAc,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,MAAM;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,kBAAkB,KAA4B;AAClD,0BAAsB,KAAK,KAAK,OAAO,QAAQ,KAAK;AAAA,MAClD,OAAO,KAAK,OAAO,QAAQ;AAAA,MAC3B,WAAW,KAAK,OAAO,QAAQ;AAAA,MAC/B,QAAQ,KAAK,OAAO,SAAS;AAAA,MAC7B,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC,CAAC;AAAA,EACH;AACF;AAOA,eAAsB,aAAa,QAAgF;AACjH,QAAM,SAAS,CAAC;AAChB,aAAW,SAAS,oBAAoB;AACtC,UAAM,MAAM,MAAM,OAAO,SAAwB,6BAA6B,KAAK,EAAE;AACrF,WAAO,KAAK,IAAI,OAAO,KAAK,KAAK,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAGA,eAAsB,gBAAgB,QAAoC;AACxE,QAAM,SAAS,MAAM,aAAa,MAAM;AACxC,SAAO,OAAO,OAAO,MAAM,EAAE,MAAM,CAAC,MAAM,MAAM,CAAC;AACnD;AAMA,eAAe,kBAAkB,IAA6B;AAC5D,aAAW,SAAS,oBAAoB;AACtC,UAAM,GAAG,KAAK,eAAe,KAAK,EAAE;AAAA,EACtC;AACF;AAUA,eAAe,4BACb,IACA,QACe;AACf,MAAI,WAAW,QAAS;AAExB,aAAW,SAAS,sBAAsB;AACxC,UAAM,MAAM,MAAM,GAAG,SAAiC,8BAA8B,KAAK,EAAE;AAC3F,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AAEV,QAAI,WAAW,YAAY;AACzB,YAAM,GAAG,QAAQ,yCAAyC,KAAK,YAAY,GAAG,SAAS;AAAA,IACzF,OAAO;AAGL,YAAM,GAAG;AAAA,QACP,mDAAmD,KAAK,MAAM,GAAG,kEAAkE,KAAK;AAAA,MAC1I;AACA,YAAM,GAAG,QAAQ,oCAAoC,GAAG,kBAAkB,KAAK,GAAG;AAAA,IACpF;AAAA,EACF;AACF;AAYA,eAAsB,iBACpB,YACA,MACA,YACA,OAAmC,EAAE,eAAe,MAAM,GAC3C;AACf,QAAM,WAAW,YAAY,OAAO,OAAO;AACzC,QAAI,KAAK,eAAe;AACtB,YAAM,kBAAkB,EAAE;AAAA,IAC5B;AAEA,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,GAAG;AAAA,QACP;AAAA,QACA;AAAA,UACE,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAEA,eAAW,MAAM,KAAK,gBAAgB;AACpC,YAAM,GAAG;AAAA,QACP;AAAA,QACA,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,GAAG;AAAA,MAC1C;AAAA,IACF;AAEA,eAAW,UAAU,KAAK,SAAS;AACjC,YAAM,GAAG;AAAA,QACP;AAAA,QACA,CAAC,OAAO,IAAI,OAAO,SAAS,OAAO,OAAO,OAAO,QAAQ,OAAO,YAAY,OAAO,cAAc,OAAO,OAAO;AAAA,MACjH;AAAA,IACF;AAEA,eAAW,WAAW,KAAK,UAAU;AACnC,YAAM,GAAG;AAAA,QACP;AAAA,QACA,CAAC,QAAQ,IAAI,QAAQ,WAAW,QAAQ,cAAc,QAAQ,SAAS,QAAQ,UAAU;AAAA,MAC3F;AAAA,IACF;AAEA,eAAW,QAAQ,KAAK,aAAa;AACnC,YAAM,GAAG;AAAA,QACP;AAAA,QACA,CAAC,KAAK,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,WAAW,KAAK,KAAK;AAAA,MACtE;AAAA,IACF;AAEA,eAAW,QAAQ,KAAK,aAAa;AACnC,YAAM,GAAG;AAAA,QACP;AAAA,QACA,CAAC,KAAK,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,WAAW,KAAK,gBAAgB,KAAK,SAAS;AAAA,MAC/F;AAAA,IACF;AAEA,UAAM,4BAA4B,IAAI,UAAU;AAAA,EAClD,CAAC;AACH;AAMO,SAAS,0BACd,OACA,YACA,KACA,QACA,SACQ;AACR,SAAO,UAAU,WACb,KAAK,wBAAwB,QAAQ,OAAO,GAAG,YAAY,IAC3D,QAAQ,KAAK,UAAU;AAC7B;AASO,SAAS,kBAAkB,QAAuB,KAAa,UAAkB,QAAQ,GAAW;AACzG,QAAM,aAAa,OAAO,QAAQ,UAAU,UAAW,OAAO,QAAQ,cAAc,sBAAuB;AAC3G,SAAO,0BAA0B,OAAO,QAAQ,OAAO,YAAY,KAAK,QAAQ,OAAO;AACzF;AAOO,SAAS,4BAA4B,QAAuB,KAAa,UAAkB,QAAQ,GAAW;AACnH,SAAO,OAAO,QAAQ,UAAU,WAC5B,KAAK,wBAAwB,QAAQ,OAAO,GAAG,YAAY,IAC3D,QAAQ,KAAK,OAAO,QAAQ,iBAAiB,IAAI;AACvD;AAKO,SAAS,sBAAsB,KAAa,YAAoB,OAA2B;AAChG,QAAM,OAAO,KAAK,QAAQ,GAAG,GAAG,YAAY,oBAAoB;AAChE,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,MAAM,MAAM;AACnE;AAKO,SAAS,qBAAqB,KAAa,YAAyC;AACzF,MAAI;AACF,UAAM,OAAO,KAAK,QAAQ,GAAG,GAAG,YAAY,oBAAoB;AAChE,QAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,eAAsB,OAAO,QAAuB,KAAa,UAAkB,QAAQ,GAAuB;AAChH,QAAM,WAAW,OAAO;AACxB,MAAI;AAEJ,MAAI,SAAS,SAAS,YAAY;AAChC,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,wBAAoB;AAC5D,aAAS,IAAI,eAAe,QAAQ;AAAA,EACtC,WAAW,SAAS,SAAS,SAAS;AACpC,UAAM,EAAE,YAAY,IAAI,MAAM,OAAO,qBAAiB;AACtD,aAAS,IAAI,YAAY,QAAQ;AAAA,EACnC,OAAO;AACL,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,sBAAkB;AACxD,QAAI,SAAS,SAAS,UAAU;AAC9B,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AAEA,QAAI;AACJ,QAAI,OAAO,QAAQ,UAAU,UAAU;AACrC,YAAM,YAAY,wBAAwB,QAAQ,OAAO;AAIzD,YAAM,oBAAoB,KAAK,WAAW,oBAAoB;AAC9D,UAAI,WAAW,iBAAiB,GAAG;AACjC,YAAI;AACF,gBAAM,gBAAgB,KAAK,MAAM,aAAa,mBAAmB,MAAM,CAAC;AACxE,cAAI,cAAc,cAAc,OAAO,QAAQ,WAAW;AACxD,kBAAM,IAAI;AAAA,cACR,sBAAsB,SAAS,kDAAkD,cAAc,SAAS;AAAA,YAC1G;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,cAAI,eAAe,SAAS,IAAI,QAAQ,SAAS,mCAAmC,EAAG,OAAM;AAAA,QAE/F;AAAA,MACF;AACA,gBAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,eAAS,KAAK,WAAW,YAAY;AAAA,IACvC,OAAO;AACL,eAAS,QAAQ,KAAK,OAAO,QAAQ,cAAc,mBAAmB;AAAA,IACxE;AAEA,aAAS,IAAI,aAAa,MAAM;AAAA,EAClC;AAEA,QAAM,OAAO,aAAa;AAC1B,SAAO,IAAI,UAAU,QAAQ,QAAQ,OAAO;AAC9C;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/drivers/mysql.ts"],"sourcesContent":["import mysql, { type ExecuteValues } from 'mysql2/promise'\n\nimport type { DBDriver } from './types'\nimport type { RemoteDBConfig } from '@/types'\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS tasks (\n id INT AUTO_INCREMENT PRIMARY KEY,\n slug VARCHAR(255) NOT NULL UNIQUE,\n title VARCHAR(500) NOT NULL,\n description TEXT,\n status VARCHAR(20) NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending','in_progress','done','blocked')),\n assigned_to VARCHAR(255),\n created_at VARCHAR(30) NOT NULL,\n started_at VARCHAR(30),\n completed_at VARCHAR(30),\n archived_at VARCHAR(30),\n updated_at VARCHAR(30) NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS task_acceptance (\n id INT AUTO_INCREMENT PRIMARY KEY,\n task_id INT NOT NULL,\n criterion TEXT NOT NULL,\n met INT NOT NULL DEFAULT 0,\n FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS actions (\n id VARCHAR(36) PRIMARY KEY,\n task_id INT NOT NULL,\n agent VARCHAR(100) NOT NULL,\n status VARCHAR(20) NOT NULL DEFAULT 'in_progress'\n CHECK(status IN ('in_progress','completed','blocked')),\n created_at VARCHAR(30) NOT NULL,\n completed_at VARCHAR(30),\n summary TEXT,\n FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS action_sections (\n id INT AUTO_INCREMENT PRIMARY KEY,\n action_id VARCHAR(36) NOT NULL,\n section_type VARCHAR(100) NOT NULL,\n content TEXT NOT NULL,\n created_at VARCHAR(30) NOT NULL,\n FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS action_files (\n id INT AUTO_INCREMENT PRIMARY KEY,\n action_id VARCHAR(36) NOT NULL,\n file_path VARCHAR(1000) NOT NULL,\n operation VARCHAR(20) NOT NULL\n CHECK(operation IN ('read','created','modified','deleted')),\n notes TEXT,\n FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS action_tools (\n id INT AUTO_INCREMENT PRIMARY KEY,\n action_id VARCHAR(36) NOT NULL,\n tool_name VARCHAR(255) NOT NULL,\n args_json TEXT,\n result_summary TEXT,\n called_at VARCHAR(30) NOT NULL,\n FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_actions_task_id ON actions(task_id);\nCREATE INDEX IF NOT EXISTS idx_actions_agent ON actions(agent);\nCREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);\nCREATE INDEX IF NOT EXISTS idx_action_files_path ON action_files(file_path(255));\nCREATE INDEX IF NOT EXISTS idx_action_tools_name ON action_tools(tool_name);\n`\n\ntype MySQLPool = mysql.Pool\n\nexport class MySQLDriver implements DBDriver {\n private pool: MySQLPool\n\n constructor(config: RemoteDBConfig) {\n this.pool = mysql.createPool(config.connectionString)\n }\n\n async ensureSchema(): Promise<void> {\n // Run each statement individually since mysql2 doesn't support multi-statement by default\n const statements = SCHEMA.split(';')\n .map((s) => s.trim())\n .filter(Boolean)\n\n const conn = await this.pool.getConnection()\n try {\n for (const stmt of statements) {\n await conn.execute(stmt)\n }\n // Migration: add archived_at column (safe to run multiple times)\n try {\n await conn.execute('ALTER TABLE tasks ADD COLUMN archived_at VARCHAR(30)')\n } catch {\n // Column already exists — ignore\n }\n // Migration: add updated_at column (safe to run multiple times)\n try {\n await conn.execute(`ALTER TABLE tasks ADD COLUMN updated_at VARCHAR(30) NOT NULL DEFAULT CURRENT_TIMESTAMP`)\n await conn.execute(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`)\n } catch {\n // Column already exists — ignore\n }\n } finally {\n conn.release()\n }\n }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const [rows] = await this.pool.execute(sql, params as ExecuteValues)\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n const rows = await this.query<T>(sql, params)\n return (rows as T[])[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.pool.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).insertId\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.pool.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).affectedRows\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.pool.query(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n const conn = await this.pool.getConnection()\n await conn.beginTransaction()\n try {\n const txDriver = new MySQLTxDriver(conn)\n const result = await fn(txDriver)\n await conn.commit()\n return result\n } catch (err) {\n await conn.rollback()\n throw err\n } finally {\n conn.release()\n }\n }\n\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n\n async close(): Promise<void> {\n await this.pool.end()\n }\n}\n\nclass MySQLTxDriver implements DBDriver {\n constructor(private conn: mysql.PoolConnection) { }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const [rows] = await this.conn.execute(sql, params as ExecuteValues)\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n return (await this.query<T>(sql, params))[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.conn.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).insertId\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const [result] = await this.conn.execute(sql, params as ExecuteValues)\n return (result as mysql.ResultSetHeader).affectedRows\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.conn.query(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n return fn(this)\n }\n\n async ensureSchema(): Promise<void> { }\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n async close(): Promise<void> { }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/drivers/postgres.ts"],"sourcesContent":["import postgres from 'postgres'\n\nimport type { DBDriver } from './types'\nimport type { RemoteDBConfig } from '@/types'\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS tasks (\n id SERIAL PRIMARY KEY,\n slug TEXT NOT NULL UNIQUE,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending','in_progress','done','blocked')),\n assigned_to TEXT,\n created_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n archived_at TEXT,\n updated_at TEXT NOT NULL DEFAULT NOW()\n);\n\nCREATE TABLE IF NOT EXISTS task_acceptance (\n id SERIAL PRIMARY KEY,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n criterion TEXT NOT NULL,\n met INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS actions (\n id TEXT PRIMARY KEY,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n agent TEXT NOT NULL\n CHECK(agent IN ('lead','explorer','builder','reviewer') OR agent LIKE 'custom:%'),\n status TEXT NOT NULL DEFAULT 'in_progress'\n CHECK(status IN ('in_progress','completed','blocked')),\n created_at TEXT NOT NULL,\n completed_at TEXT,\n summary TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_sections (\n id SERIAL PRIMARY KEY,\n action_id TEXT NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n section_type TEXT NOT NULL,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS action_files (\n id SERIAL PRIMARY KEY,\n action_id TEXT NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n file_path TEXT NOT NULL,\n operation TEXT NOT NULL\n CHECK(operation IN ('read','created','modified','deleted')),\n notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_tools (\n id SERIAL PRIMARY KEY,\n action_id TEXT NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n tool_name TEXT NOT NULL,\n args_json TEXT,\n result_summary TEXT,\n called_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_actions_task_id ON actions(task_id);\nCREATE INDEX IF NOT EXISTS idx_actions_agent ON actions(agent);\nCREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);\nCREATE INDEX IF NOT EXISTS idx_action_files_path ON action_files(file_path);\nCREATE INDEX IF NOT EXISTS idx_action_tools_name ON action_tools(tool_name);\n`\n\n// Convert SQLite-style ? placeholders to Postgres $1, $2, ...\nfunction toPositional(sql: string): string {\n let i = 0\n return sql.replace(/\\?/g, () => `$${++i}`)\n}\n\nexport class PostgresDriver implements DBDriver {\n private sql: postgres.Sql\n\n constructor(config: RemoteDBConfig) {\n this.sql = postgres(config.connectionString)\n }\n\n async ensureSchema(): Promise<void> {\n await this.sql.unsafe(SCHEMA)\n // Migration: add archived_at column (safe to run multiple times)\n try {\n await this.sql.unsafe(`ALTER TABLE tasks ADD COLUMN archived_at TEXT`)\n } catch {\n // Column already exists — ignore\n }\n // Migration: add updated_at column (safe to run multiple times)\n try {\n await this.sql.unsafe(`ALTER TABLE tasks ADD COLUMN updated_at TEXT NOT NULL DEFAULT NOW()`)\n await this.sql.unsafe(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`)\n } catch {\n // Column already exists — ignore\n }\n }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const rows = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n const rows = await this.query<T>(sql, params)\n return rows[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const withReturning = toPositional(sql.trimEnd()) + ' RETURNING id'\n const rows = await this.sql.unsafe(withReturning, params as postgres.ParameterOrJSON<never>[])\n return (rows[0] as unknown as { id: number }).id\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const result = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return result.count\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.sql.unsafe(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n let result!: T\n await this.sql.begin(async (txSql) => {\n const txDriver = new PostgresTxDriver(txSql)\n result = await fn(txDriver)\n })\n return result\n }\n\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n\n async close(): Promise<void> {\n await this.sql.end()\n }\n}\n\nclass PostgresTxDriver implements DBDriver {\n constructor(private sql: postgres.TransactionSql) { }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const rows = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return rows as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n return (await this.query<T>(sql, params))[0] ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n const withReturning = toPositional(sql.trimEnd()) + ' RETURNING id'\n const rows = await this.sql.unsafe(withReturning, params as postgres.ParameterOrJSON<never>[])\n return (rows[0] as unknown as { id: number }).id\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const result = await this.sql.unsafe(toPositional(sql), params as postgres.ParameterOrJSON<never>[])\n return result.count\n }\n\n async execRaw(sql: string): Promise<void> {\n await this.sql.unsafe(sql)\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n return fn(this)\n }\n\n async ensureSchema(): Promise<void> { }\n async reconnect(): Promise<void> {\n /* no-op — connection pool handles freshness */\n }\n async close(): Promise<void> { }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/drivers/sqlite.ts","../src/core/sqlite-adapter.ts"],"sourcesContent":["import { existsSync, mkdirSync, rmSync, statSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\nimport { lastInsertId, openSQLite, type SQLiteDB } from '../sqlite-adapter'\n\nimport type { DBDriver } from './types'\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS tasks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n slug TEXT NOT NULL UNIQUE,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending','in_progress','done','blocked')),\n assigned_to TEXT,\n created_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n archived_at TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS task_acceptance (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n criterion TEXT NOT NULL,\n met INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS actions (\n id TEXT PRIMARY KEY,\n task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,\n agent TEXT NOT NULL\n CHECK(agent IN ('lead','explorer','builder','reviewer') OR agent LIKE 'custom:%'),\n status TEXT NOT NULL DEFAULT 'in_progress'\n CHECK(status IN ('in_progress','completed','blocked')),\n created_at TEXT NOT NULL,\n completed_at TEXT,\n summary TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_sections (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n action_id TEXT NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n section_type TEXT NOT NULL,\n content TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS action_files (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n action_id TEXT NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n file_path TEXT NOT NULL,\n operation TEXT NOT NULL\n CHECK(operation IN ('read','created','modified','deleted')),\n notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS action_tools (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n action_id TEXT NOT NULL REFERENCES actions(id) ON DELETE CASCADE,\n tool_name TEXT NOT NULL,\n args_json TEXT,\n result_summary TEXT,\n called_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\nCREATE INDEX IF NOT EXISTS idx_actions_task_id ON actions(task_id);\nCREATE INDEX IF NOT EXISTS idx_actions_agent ON actions(agent);\nCREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);\nCREATE INDEX IF NOT EXISTS idx_action_files_path ON action_files(file_path);\nCREATE INDEX IF NOT EXISTS idx_action_tools_name ON action_tools(tool_name);\n`\n\nexport class SQLiteDriver implements DBDriver {\n private db: SQLiteDB\n private dbPath: string\n\n constructor(dbPath: string) {\n this.dbPath = dbPath\n mkdirSync(dirname(dbPath), { recursive: true })\n // Remove stale WAL/SHM files left by a crashed session — they cause SQLITE_IOERR.\n // A 0-byte WAL alongside a non-empty SHM means the last checkpoint never completed.\n if (existsSync(dbPath)) {\n const shm = `${dbPath}-shm`\n const wal = `${dbPath}-wal`\n if (existsSync(shm) && existsSync(wal) && statSync(wal).size === 0) {\n rmSync(shm, { force: true })\n rmSync(wal, { force: true })\n }\n }\n this.db = openSQLite(dbPath)\n this.db.exec('PRAGMA journal_mode = WAL')\n this.db.exec('PRAGMA foreign_keys = ON')\n }\n\n async ensureSchema(): Promise<void> {\n this.db.exec(SCHEMA)\n // Migration: add archived_at column (safe to run multiple times)\n try {\n this.db.exec('ALTER TABLE tasks ADD COLUMN archived_at TEXT')\n } catch {\n // Column already exists — ignore\n }\n // Migration: add updated_at column (safe to run multiple times)\n try {\n this.db.exec(`ALTER TABLE tasks ADD COLUMN updated_at TEXT`)\n this.db.exec(`UPDATE tasks SET updated_at = COALESCE(completed_at, started_at, created_at) WHERE updated_at IS NULL OR updated_at = ''`)\n } catch {\n // Column already exists — ignore\n }\n }\n\n async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n return this.db.prepare(sql).all(...params) as unknown as T[]\n }\n\n async queryOne<T>(sql: string, params: unknown[] = []): Promise<T | null> {\n return (this.db.prepare(sql).get(...params) as unknown as T) ?? null\n }\n\n async insert(sql: string, params: unknown[] = []): Promise<number> {\n this.db.prepare(sql).run(...params)\n return lastInsertId(this.db)\n }\n\n async exec(sql: string, params: unknown[] = []): Promise<number> {\n const result = this.db.prepare(sql).run(...params)\n return (result as { changes?: number }).changes ?? 0\n }\n\n async execRaw(sql: string): Promise<void> {\n this.db.exec(sql)\n }\n\n async reconnect(): Promise<void> {\n this.db.close()\n this.db = openSQLite(this.dbPath)\n this.db.exec('PRAGMA journal_mode = WAL')\n this.db.exec('PRAGMA foreign_keys = ON')\n }\n\n async transaction<T>(fn: (tx: DBDriver) => Promise<T>): Promise<T> {\n this.db.exec('BEGIN IMMEDIATE')\n try {\n const result = await fn(this)\n this.db.exec('COMMIT')\n return result\n } catch (err) {\n this.db.exec('ROLLBACK')\n throw err\n }\n }\n\n async close(): Promise<void> {\n this.db.close()\n }\n}\n","import { createRequire } from 'node:module'\n\nconst _require = createRequire(import.meta.url)\nconst isBun = 'bun' in process.versions\n\n// ─── Shared interface ─────────────────────────────────────────────────────────\n// Both better-sqlite3 (Database) and bun:sqlite (Database) expose this surface.\n\nexport type SQLRow = Record<string, unknown>\n\nexport interface SQLStatement {\n run(...args: unknown[]): unknown\n get(...args: unknown[]): SQLRow | undefined\n all(...args: unknown[]): SQLRow[]\n}\n\nexport interface SQLiteDB {\n exec(sql: string): void\n prepare(sql: string): SQLStatement\n close(): void\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function openSQLite(path: string): SQLiteDB {\n if (isBun) {\n const { Database } = _require('bun:sqlite') as {\n Database: new (path: string) => unknown\n }\n return new Database(path) as unknown as SQLiteDB\n }\n\n const Database = _require('better-sqlite3') as new (path: string) => unknown\n return new Database(path) as unknown as SQLiteDB\n}\n\n// ─── last_insert_rowid() helper ───────────────────────────────────────────────\n// Both bun:sqlite and better-sqlite3 have different return types for stmt.run().\n// Reading last_insert_rowid() directly avoids the inconsistency.\n\nexport function lastInsertId(db: SQLiteDB): number {\n const row = db.prepare('SELECT last_insert_rowid() AS id').get() as { id: number }\n return row.id\n}\n"],"mappings":"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","names":["Database"]}
|
|
File without changes
|