argus-code 0.2.0__py3-none-any.whl
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.
- argus/__init__.py +3 -0
- argus/adapters/__init__.py +7 -0
- argus/adapters/base.py +108 -0
- argus/adapters/claude_code/__init__.py +5 -0
- argus/adapters/claude_code/adapter.py +63 -0
- argus/adapters/claude_code/discover.py +72 -0
- argus/adapters/claude_code/extract_tool_calls.py +86 -0
- argus/adapters/claude_code/extract_transcript.py +111 -0
- argus/adapters/claude_code/extract_turns.py +69 -0
- argus/adapters/claude_code/history_jsonl.py +138 -0
- argus/adapters/claude_code/ingest_file.py +137 -0
- argus/adapters/claude_code/model.py +11 -0
- argus/adapters/claude_code/schemas.py +77 -0
- argus/adapters/registry.py +30 -0
- argus/cli.py +384 -0
- argus/collector/__init__.py +0 -0
- argus/collector/aggregate.py +102 -0
- argus/collector/first_run.py +189 -0
- argus/collector/pipeline.py +140 -0
- argus/collector/rollup_subagents.py +27 -0
- argus/collector/scheduler.py +89 -0
- argus/collector/search_backfill.py +109 -0
- argus/collector/watcher.py +178 -0
- argus/dashboard-dist/_astro/charts.BIevw6Es.js +1 -0
- argus/dashboard-dist/_astro/format.DxC1NGYT.js +1 -0
- argus/dashboard-dist/_astro/index.astro_astro_type_script_index_0_lang.CgwSARdD.js +24 -0
- argus/dashboard-dist/_astro/index.astro_astro_type_script_index_0_lang.W18SJsr7.js +11 -0
- argus/dashboard-dist/_astro/installCanvasRenderer.D_tC6TXz.js +18 -0
- argus/dashboard-dist/_astro/models.astro_astro_type_script_index_0_lang.BHTHXYHC.js +13 -0
- argus/dashboard-dist/_astro/prompts.astro_astro_type_script_index_0_lang.DfNgiDv9.js +17 -0
- argus/dashboard-dist/_astro/session.astro_astro_type_script_index_0_lang.Dj_bfrIa.js +86 -0
- argus/dashboard-dist/_astro/settings.astro_astro_type_script_index_0_lang.d_a-uvdi.js +24 -0
- argus/dashboard-dist/_astro/tools.astro_astro_type_script_index_0_lang.Dzzau3Yt.js +12 -0
- argus/dashboard-dist/_astro/trends.astro_astro_type_script_index_0_lang.BLLeGRNa.js +5 -0
- argus/dashboard-dist/index.html +2 -0
- argus/dashboard-dist/models/index.html +1 -0
- argus/dashboard-dist/prompts/index.html +18 -0
- argus/dashboard-dist/session/index.html +2 -0
- argus/dashboard-dist/sessions/index.html +1 -0
- argus/dashboard-dist/settings/index.html +8 -0
- argus/dashboard-dist/styles/global.css +307 -0
- argus/dashboard-dist/tools/index.html +1 -0
- argus/dashboard-dist/trends/index.html +1 -0
- argus/detectors/__init__.py +6 -0
- argus/detectors/base.py +34 -0
- argus/detectors/registry.py +20 -0
- argus/detectors/tool_error_rate_spike.py +138 -0
- argus/pricing/2026-05-02.json +24 -0
- argus/pricing/__init__.py +0 -0
- argus/pricing/compute.py +46 -0
- argus/pricing/load.py +45 -0
- argus/pricing/refresh.py +91 -0
- argus/pricing/types.py +21 -0
- argus/scaffold/__init__.py +0 -0
- argus/scaffold/scaffolder.py +45 -0
- argus/scaffold/snapshot.py +73 -0
- argus/scaffold/storage.py +60 -0
- argus/schema/__init__.py +0 -0
- argus/schema/types.py +157 -0
- argus/server/__init__.py +0 -0
- argus/server/api.py +661 -0
- argus/server/app.py +97 -0
- argus/store/__init__.py +0 -0
- argus/store/db.py +103 -0
- argus/store/migrations/__init__.py +0 -0
- argus/store/migrations/inline.py +180 -0
- argus/store/repository.py +778 -0
- argus/templates/default/.claude/agents/code-reviewer.md +27 -0
- argus/templates/default/.claude/agents/security-auditor.md +28 -0
- argus/templates/default/.claude/commands/commit.md +38 -0
- argus/templates/default/.claude/commands/deploy.md +13 -0
- argus/templates/default/.claude/commands/fix-issue.md +15 -0
- argus/templates/default/.claude/commands/pr.md +38 -0
- argus/templates/default/.claude/commands/review.md +14 -0
- argus/templates/default/.claude/rules/api-conventions.md +27 -0
- argus/templates/default/.claude/rules/code-style.md +25 -0
- argus/templates/default/.claude/rules/testing.md +19 -0
- argus/templates/default/.claude/settings.json +28 -0
- argus/templates/default/.claude/skills/example/SKILL.md +11 -0
- argus/templates/default/CLAUDE.md +57 -0
- argus_code-0.2.0.dist-info/METADATA +247 -0
- argus_code-0.2.0.dist-info/RECORD +86 -0
- argus_code-0.2.0.dist-info/WHEEL +4 -0
- argus_code-0.2.0.dist-info/entry_points.txt +2 -0
- argus_code-0.2.0.dist-info/licenses/LICENSE +21 -0
- argus_code-0.2.0.dist-info/licenses/NOTICE +22 -0
argus/server/app.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""FastAPI app builder + uvicorn launcher."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import uvicorn
|
|
11
|
+
from fastapi import FastAPI, Request
|
|
12
|
+
from fastapi.exceptions import RequestValidationError
|
|
13
|
+
from fastapi.responses import JSONResponse
|
|
14
|
+
from fastapi.staticfiles import StaticFiles
|
|
15
|
+
|
|
16
|
+
from ..adapters.base import Adapter
|
|
17
|
+
from ..collector.first_run import FirstRunHandle
|
|
18
|
+
from ..pricing.types import PricingTable
|
|
19
|
+
from ..store.repository import Repository
|
|
20
|
+
from .api import ApiDeps, build_api, is_allowed_origin
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ServerOpts:
|
|
27
|
+
pricing_table_version: str
|
|
28
|
+
ingest_status: Any # callable returning IngestStatus
|
|
29
|
+
dashboard_dir: Path
|
|
30
|
+
port: int
|
|
31
|
+
adapters: list[Adapter]
|
|
32
|
+
pricing_table: PricingTable
|
|
33
|
+
host: str = "127.0.0.1"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_app(repo: Repository, opts: ServerOpts) -> FastAPI:
|
|
37
|
+
"""Build the FastAPI app with CSRF middleware, API routes, static mount."""
|
|
38
|
+
app = FastAPI(title="Argus", docs_url=None, redoc_url=None, openapi_url=None)
|
|
39
|
+
|
|
40
|
+
@app.exception_handler(RequestValidationError)
|
|
41
|
+
async def on_validation_error(request: Request, exc: RequestValidationError):
|
|
42
|
+
return JSONResponse(
|
|
43
|
+
{"error": "bad request", "detail": exc.errors()}, status_code=400
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# CSRF origin guard for state-changing requests. GETs are unrestricted
|
|
47
|
+
# because the dashboard issues same-origin GETs without an Origin
|
|
48
|
+
# header in some setups; POSTs from browsers always carry Origin.
|
|
49
|
+
@app.middleware("http")
|
|
50
|
+
async def csrf_origin_check(request: Request, call_next):
|
|
51
|
+
method = request.method
|
|
52
|
+
if method in ("GET", "HEAD", "OPTIONS"):
|
|
53
|
+
return await call_next(request)
|
|
54
|
+
# Allow API-prefixed paths to enforce the check; non-API endpoints
|
|
55
|
+
# don't exist on this server today, but apply uniformly.
|
|
56
|
+
if not is_allowed_origin(request.headers.get("origin")):
|
|
57
|
+
return JSONResponse(
|
|
58
|
+
{"error": "cross-origin requests not allowed"}, status_code=403
|
|
59
|
+
)
|
|
60
|
+
return await call_next(request)
|
|
61
|
+
|
|
62
|
+
api = build_api(
|
|
63
|
+
repo,
|
|
64
|
+
ApiDeps(
|
|
65
|
+
pricing_table_version=opts.pricing_table_version,
|
|
66
|
+
ingest_status=opts.ingest_status,
|
|
67
|
+
adapters=opts.adapters,
|
|
68
|
+
pricing_table=opts.pricing_table,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
app.include_router(api)
|
|
72
|
+
|
|
73
|
+
# Static dashboard mount LAST so /api/* routes win.
|
|
74
|
+
if opts.dashboard_dir.exists():
|
|
75
|
+
app.mount(
|
|
76
|
+
"/",
|
|
77
|
+
StaticFiles(directory=str(opts.dashboard_dir), html=True),
|
|
78
|
+
name="dashboard",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return app
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def serve_blocking(app: FastAPI, *, host: str, port: int) -> None:
|
|
85
|
+
"""Run uvicorn in the current thread until SIGINT or .should_exit."""
|
|
86
|
+
config = uvicorn.Config(
|
|
87
|
+
app,
|
|
88
|
+
host=host,
|
|
89
|
+
port=port,
|
|
90
|
+
log_level="warning", # quiet access log; user opts in via --verbose
|
|
91
|
+
access_log=False,
|
|
92
|
+
)
|
|
93
|
+
server = uvicorn.Server(config)
|
|
94
|
+
try:
|
|
95
|
+
server.run()
|
|
96
|
+
except KeyboardInterrupt:
|
|
97
|
+
pass
|
argus/store/__init__.py
ADDED
|
File without changes
|
argus/store/db.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""SQLite connection opener + migration runner.
|
|
2
|
+
|
|
3
|
+
Verifies FTS5 is compiled in at startup; failing fast with a clear error
|
|
4
|
+
is better than a confusing OperationalError deep in searchPrompts later.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sqlite3
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .migrations.inline import (
|
|
12
|
+
MIGRATION_001,
|
|
13
|
+
MIGRATION_002,
|
|
14
|
+
MIGRATION_003,
|
|
15
|
+
MIGRATION_004,
|
|
16
|
+
MIGRATION_005,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
SCHEMA_VERSION = 5
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FTS5NotAvailableError(RuntimeError):
|
|
23
|
+
"""Raised when the Python sqlite3 build lacks FTS5 support."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _assert_fts5(conn: sqlite3.Connection) -> None:
|
|
27
|
+
"""Fail loudly if FTS5 isn't compiled into this Python's sqlite3.
|
|
28
|
+
|
|
29
|
+
CPython's standard distributions for Windows / macOS / Linux all ship
|
|
30
|
+
FTS5 since 3.11. Alpine minimal builds and some custom builds don't.
|
|
31
|
+
"""
|
|
32
|
+
cur = conn.execute("PRAGMA compile_options")
|
|
33
|
+
opts = {row[0] for row in cur.fetchall()}
|
|
34
|
+
if "ENABLE_FTS5" not in opts:
|
|
35
|
+
raise FTS5NotAvailableError(
|
|
36
|
+
"Your Python's sqlite3 was built without FTS5 (ENABLE_FTS5). "
|
|
37
|
+
"Argus requires FTS5 for prompt and transcript search. "
|
|
38
|
+
"Use the official CPython distribution or build sqlite with FTS5."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def open_db(path: str | Path) -> sqlite3.Connection:
|
|
43
|
+
"""Open the Argus SQLite DB at ``path``, applying migrations 1..N."""
|
|
44
|
+
p = Path(path)
|
|
45
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
# check_same_thread=False because the Repository may be accessed from
|
|
48
|
+
# the request thread, the watcher thread, the first-run worker, and
|
|
49
|
+
# the scheduler thread. SQLite itself is thread-safe in serialized
|
|
50
|
+
# mode; better-sqlite3 in TS makes the same assumption.
|
|
51
|
+
#
|
|
52
|
+
# cached_statements=0 disables Python sqlite3's per-Connection
|
|
53
|
+
# prepared-statement cache. With the cache enabled, two threads that
|
|
54
|
+
# call execute(SAME_SQL, ...) concurrently can both pull the cached
|
|
55
|
+
# sqlite3_stmt*, both call reset+bind on it, and one loses with
|
|
56
|
+
# SQLITE_MISUSE ("bad parameter or other API misuse"). The cost of
|
|
57
|
+
# disabling the cache is ~10µs of statement prep per query, which is
|
|
58
|
+
# well below the cost of the queries themselves. SQLite's own internal
|
|
59
|
+
# compilation cache (sqlite3_prepare_v2 fast path) still applies.
|
|
60
|
+
conn = sqlite3.connect(
|
|
61
|
+
str(p),
|
|
62
|
+
check_same_thread=False,
|
|
63
|
+
isolation_level=None,
|
|
64
|
+
cached_statements=0,
|
|
65
|
+
)
|
|
66
|
+
conn.row_factory = sqlite3.Row
|
|
67
|
+
conn.execute("PRAGMA journal_mode = WAL")
|
|
68
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
|
69
|
+
conn.execute("PRAGMA busy_timeout = 5000")
|
|
70
|
+
|
|
71
|
+
_assert_fts5(conn)
|
|
72
|
+
|
|
73
|
+
# MIGRATION_001 is fully idempotent (CREATE TABLE IF NOT EXISTS) so we
|
|
74
|
+
# always run it. After this point app_meta is guaranteed to exist.
|
|
75
|
+
conn.executescript(MIGRATION_001)
|
|
76
|
+
|
|
77
|
+
# Versioned migrations: ALTER TABLE has no IF NOT EXISTS in SQLite, so
|
|
78
|
+
# we gate later migrations on a schema_version row. Fresh DBs start at
|
|
79
|
+
# 1 (everything in MIGRATION_001 has been applied) and step up.
|
|
80
|
+
row = conn.execute(
|
|
81
|
+
"SELECT value FROM app_meta WHERE key = 'schema_version'"
|
|
82
|
+
).fetchone()
|
|
83
|
+
current = int(row["value"]) if row else 1
|
|
84
|
+
|
|
85
|
+
if current < 2:
|
|
86
|
+
conn.executescript(MIGRATION_002)
|
|
87
|
+
current = 2
|
|
88
|
+
if current < 3:
|
|
89
|
+
conn.executescript(MIGRATION_003)
|
|
90
|
+
current = 3
|
|
91
|
+
if current < 4:
|
|
92
|
+
conn.executescript(MIGRATION_004)
|
|
93
|
+
current = 4
|
|
94
|
+
if current < 5:
|
|
95
|
+
conn.executescript(MIGRATION_005)
|
|
96
|
+
current = 5
|
|
97
|
+
|
|
98
|
+
conn.execute(
|
|
99
|
+
"INSERT OR REPLACE INTO app_meta (key, value) VALUES ('schema_version', ?)",
|
|
100
|
+
(str(SCHEMA_VERSION),),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return conn
|
|
File without changes
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Schema migrations.
|
|
2
|
+
|
|
3
|
+
Append-only. Never edit a published migration — production users have data
|
|
4
|
+
that depends on the exact SQL that already ran. To change the schema, add
|
|
5
|
+
a new MIGRATION_N+1 and bump SCHEMA_VERSION in db.py.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
MIGRATION_001 = """
|
|
9
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
agent TEXT NOT NULL,
|
|
12
|
+
agent_version TEXT,
|
|
13
|
+
project_path TEXT NOT NULL,
|
|
14
|
+
started_at TEXT NOT NULL,
|
|
15
|
+
ended_at TEXT,
|
|
16
|
+
duration_sec INTEGER,
|
|
17
|
+
total_fresh_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
18
|
+
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
total_cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
total_cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
21
|
+
total_cost_usd REAL NOT NULL DEFAULT 0,
|
|
22
|
+
primary_model TEXT NOT NULL,
|
|
23
|
+
turn_count INTEGER NOT NULL DEFAULT 0,
|
|
24
|
+
pricing_table_version TEXT NOT NULL,
|
|
25
|
+
computed_at TEXT NOT NULL,
|
|
26
|
+
agent_reported_cost_usd REAL,
|
|
27
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at DESC);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS turns (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
36
|
+
sequence INTEGER NOT NULL,
|
|
37
|
+
timestamp TEXT NOT NULL,
|
|
38
|
+
model TEXT NOT NULL,
|
|
39
|
+
model_raw TEXT NOT NULL,
|
|
40
|
+
fresh_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
43
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
44
|
+
cache_write_5m_tokens INTEGER,
|
|
45
|
+
cache_write_1h_tokens INTEGER,
|
|
46
|
+
tool_calls_count INTEGER NOT NULL DEFAULT 0,
|
|
47
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
48
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_turns_session ON turns(session_id, sequence);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE IF NOT EXISTS file_offsets (
|
|
54
|
+
path TEXT PRIMARY KEY,
|
|
55
|
+
byte_offset INTEGER NOT NULL,
|
|
56
|
+
last_seen TEXT NOT NULL
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS parse_errors (
|
|
60
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
61
|
+
file TEXT NOT NULL,
|
|
62
|
+
byte_offset INTEGER NOT NULL,
|
|
63
|
+
reason TEXT NOT NULL,
|
|
64
|
+
raw_line_truncated TEXT NOT NULL,
|
|
65
|
+
occurred_at TEXT NOT NULL
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS app_meta (
|
|
69
|
+
key TEXT PRIMARY KEY,
|
|
70
|
+
value TEXT NOT NULL
|
|
71
|
+
);
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
MIGRATION_002 = """
|
|
75
|
+
ALTER TABLE sessions ADD COLUMN started_at_ms INTEGER;
|
|
76
|
+
ALTER TABLE sessions ADD COLUMN ended_at_ms INTEGER;
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_time ON sessions(project_path, started_at_ms, ended_at_ms);
|
|
78
|
+
|
|
79
|
+
UPDATE sessions
|
|
80
|
+
SET started_at_ms = CAST(strftime('%s', started_at) AS INTEGER) * 1000,
|
|
81
|
+
ended_at_ms = CASE WHEN ended_at IS NULL OR ended_at = '' THEN NULL
|
|
82
|
+
ELSE CAST(strftime('%s', ended_at) AS INTEGER) * 1000 END
|
|
83
|
+
WHERE started_at_ms IS NULL;
|
|
84
|
+
|
|
85
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
86
|
+
id TEXT PRIMARY KEY,
|
|
87
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
88
|
+
turn_index INTEGER NOT NULL,
|
|
89
|
+
tool_name TEXT NOT NULL,
|
|
90
|
+
is_error INTEGER NOT NULL DEFAULT 0,
|
|
91
|
+
input_size INTEGER NOT NULL DEFAULT 0,
|
|
92
|
+
subagent_type TEXT,
|
|
93
|
+
timestamp TEXT NOT NULL
|
|
94
|
+
);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_name ON tool_calls(tool_name);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_ts ON tool_calls(timestamp);
|
|
98
|
+
|
|
99
|
+
CREATE TABLE IF NOT EXISTS prompts (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
timestamp_ms INTEGER NOT NULL,
|
|
102
|
+
project_path TEXT NOT NULL,
|
|
103
|
+
display TEXT NOT NULL,
|
|
104
|
+
pasted_chars INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
is_slash INTEGER NOT NULL DEFAULT 0
|
|
106
|
+
);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_ts ON prompts(timestamp_ms);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_prompts_project ON prompts(project_path);
|
|
109
|
+
|
|
110
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS prompts_fts USING fts5(
|
|
111
|
+
display,
|
|
112
|
+
content='prompts',
|
|
113
|
+
content_rowid='id',
|
|
114
|
+
tokenize='unicode61'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON prompts BEGIN
|
|
118
|
+
INSERT INTO prompts_fts(rowid, display) VALUES (new.id, new.display);
|
|
119
|
+
END;
|
|
120
|
+
CREATE TRIGGER IF NOT EXISTS prompts_ad AFTER DELETE ON prompts BEGIN
|
|
121
|
+
INSERT INTO prompts_fts(prompts_fts, rowid, display) VALUES('delete', old.id, old.display);
|
|
122
|
+
END;
|
|
123
|
+
CREATE TRIGGER IF NOT EXISTS prompts_au AFTER UPDATE ON prompts BEGIN
|
|
124
|
+
INSERT INTO prompts_fts(prompts_fts, rowid, display) VALUES('delete', old.id, old.display);
|
|
125
|
+
INSERT INTO prompts_fts(rowid, display) VALUES (new.id, new.display);
|
|
126
|
+
END;
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
MIGRATION_003 = """
|
|
130
|
+
CREATE TABLE IF NOT EXISTS transcript_segments (
|
|
131
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
132
|
+
uid TEXT UNIQUE NOT NULL,
|
|
133
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
134
|
+
timestamp TEXT NOT NULL,
|
|
135
|
+
role TEXT NOT NULL,
|
|
136
|
+
text TEXT NOT NULL
|
|
137
|
+
);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_segments_session ON transcript_segments(session_id);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_segments_ts ON transcript_segments(timestamp);
|
|
140
|
+
|
|
141
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS transcript_fts USING fts5(
|
|
142
|
+
text,
|
|
143
|
+
content='transcript_segments',
|
|
144
|
+
content_rowid='rowid',
|
|
145
|
+
tokenize='unicode61'
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
CREATE TRIGGER IF NOT EXISTS segments_ai AFTER INSERT ON transcript_segments BEGIN
|
|
149
|
+
INSERT INTO transcript_fts(rowid, text) VALUES (new.rowid, new.text);
|
|
150
|
+
END;
|
|
151
|
+
CREATE TRIGGER IF NOT EXISTS segments_ad AFTER DELETE ON transcript_segments BEGIN
|
|
152
|
+
INSERT INTO transcript_fts(transcript_fts, rowid, text) VALUES('delete', old.rowid, old.text);
|
|
153
|
+
END;
|
|
154
|
+
CREATE TRIGGER IF NOT EXISTS segments_au AFTER UPDATE ON transcript_segments BEGIN
|
|
155
|
+
INSERT INTO transcript_fts(transcript_fts, rowid, text) VALUES('delete', old.rowid, old.text);
|
|
156
|
+
INSERT INTO transcript_fts(rowid, text) VALUES (new.rowid, new.text);
|
|
157
|
+
END;
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
MIGRATION_004 = """
|
|
161
|
+
CREATE TABLE IF NOT EXISTS alerts (
|
|
162
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
163
|
+
detector TEXT NOT NULL,
|
|
164
|
+
dedup_key TEXT NOT NULL,
|
|
165
|
+
severity TEXT NOT NULL CHECK (severity IN ('info', 'warning', 'critical')),
|
|
166
|
+
title TEXT NOT NULL,
|
|
167
|
+
message TEXT NOT NULL,
|
|
168
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
169
|
+
first_seen_at TEXT NOT NULL,
|
|
170
|
+
last_seen_at TEXT NOT NULL,
|
|
171
|
+
seen_at TEXT,
|
|
172
|
+
UNIQUE (detector, dedup_key)
|
|
173
|
+
);
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_alerts_unseen ON alerts(seen_at, severity);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_alerts_recent ON alerts(last_seen_at DESC);
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
MIGRATION_005 = """
|
|
179
|
+
ALTER TABLE alerts ADD COLUMN resolved_at TEXT;
|
|
180
|
+
"""
|