ndomo 0.1.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/.bun-version +1 -0
- package/.dockerignore +79 -0
- package/.editorconfig +18 -0
- package/.env.example +19 -0
- package/.github/CODEOWNERS +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
- package/.github/ISSUE_TEMPLATE/config.yml +2 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
- package/.github/dependabot.yml +36 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/release.yml +30 -0
- package/.github/workflows/gitleaks.yml +28 -0
- package/.github/workflows/release-please.yml +27 -0
- package/.github/workflows/smoke.yml +29 -0
- package/.husky/commit-msg +1 -0
- package/CHANGELOG.md +114 -0
- package/Dockerfile +32 -0
- package/README.es.md +174 -0
- package/README.md +187 -0
- package/agents/chronicler.md +98 -0
- package/agents/ci-smith.md +136 -0
- package/agents/craftsman.md +341 -0
- package/agents/deploy-smith.md +138 -0
- package/agents/foreman.md +377 -0
- package/agents/go-smith.md +164 -0
- package/agents/guild.md +188 -0
- package/agents/inspector.md +83 -0
- package/agents/js-smith.md +127 -0
- package/agents/ops-scout.md +173 -0
- package/agents/painter.md +200 -0
- package/agents/python-smith.md +120 -0
- package/agents/ranger.md +307 -0
- package/agents/release-smith.md +165 -0
- package/agents/rust-smith.md +159 -0
- package/agents/sage.md +178 -0
- package/agents/scout.md +144 -0
- package/agents/scribe.md +156 -0
- package/agents/smith.md +201 -0
- package/agents/vue-smith.md +155 -0
- package/agents/warden.md +216 -0
- package/agents/zig-smith.md +156 -0
- package/bin/ndomo-analyses.ts +4 -0
- package/bin/ndomo-status.ts +4 -0
- package/biome.json +57 -0
- package/bun.lock +514 -0
- package/commitlint.config.js +3 -0
- package/config/ndomo.config.json +258 -0
- package/config/ndomo.schema.json +166 -0
- package/docs/agents.md +375 -0
- package/docs/bugs/plan-create-orphan-fk.md +131 -0
- package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
- package/docs/configuration.md +276 -0
- package/docs/database.md +364 -0
- package/docs/features/feature-flexible-builder-v1.md +724 -0
- package/docs/features/feature-flexible-builder-v2.md +882 -0
- package/docs/features/feature-flexible-builder.md +974 -0
- package/docs/http-server.md +244 -0
- package/docs/installation.md +259 -0
- package/docs/integrations.md +129 -0
- package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
- package/docs/operations/audit-v1.md +417 -0
- package/docs/operations/audit-v2.md +197 -0
- package/docs/operations/audit-v3.md +306 -0
- package/docs/operations/db-optimize-foundations.md +123 -0
- package/docs/operations/verify-gate-architecture.md +82 -0
- package/docs/workflows.md +448 -0
- package/opencode.json +5 -0
- package/package.json +65 -0
- package/release-please-config.json +11 -0
- package/scripts/dev-bust-cache.sh +164 -0
- package/scripts/install.sh +688 -0
- package/scripts/smoke-e2e.ts +704 -0
- package/scripts/smoke-hot.ts +417 -0
- package/scripts/smoke-http.sh +228 -0
- package/scripts/smoke-v4.ts +256 -0
- package/scripts/smoke-v5.ts +397 -0
- package/scripts/smoke.sh +9 -0
- package/scripts/uninstall.sh +224 -0
- package/skills/api-security-best-practices/SKILL.md +915 -0
- package/skills/bash-scripting/SKILL.md +201 -0
- package/skills/bun/SKILL.md +313 -0
- package/skills/cavecrew/SKILL.md +82 -0
- package/skills/caveman/SKILL.md +74 -0
- package/skills/caveman-review/README.md +33 -0
- package/skills/caveman-review/SKILL.md +55 -0
- package/skills/find-skills/SKILL.md +142 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +55 -0
- package/skills/golang-patterns/SKILL.md +674 -0
- package/skills/golang-security/SKILL.md +185 -0
- package/skills/golang-security/evals/evals.json +595 -0
- package/skills/golang-security/references/architecture.md +268 -0
- package/skills/golang-security/references/checklist.md +80 -0
- package/skills/golang-security/references/cookies.md +200 -0
- package/skills/golang-security/references/cryptography.md +424 -0
- package/skills/golang-security/references/filesystem.md +285 -0
- package/skills/golang-security/references/injection.md +315 -0
- package/skills/golang-security/references/logging.md +163 -0
- package/skills/golang-security/references/memory-safety.md +241 -0
- package/skills/golang-security/references/network.md +253 -0
- package/skills/golang-security/references/secrets.md +189 -0
- package/skills/golang-security/references/third-party.md +159 -0
- package/skills/golang-security/references/threat-modeling.md +189 -0
- package/skills/golang-testing/SKILL.md +720 -0
- package/skills/grill-me/SKILL.md +7 -0
- package/skills/javascript-testing-patterns/SKILL.md +537 -0
- package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
- package/skills/modern-javascript-patterns/SKILL.md +43 -0
- package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
- package/skills/modern-javascript-patterns/references/details.md +457 -0
- package/skills/python-anti-patterns/SKILL.md +349 -0
- package/skills/python-design-patterns/SKILL.md +85 -0
- package/skills/python-design-patterns/references/details.md +353 -0
- package/skills/python-error-handling/SKILL.md +193 -0
- package/skills/python-error-handling/references/details.md +171 -0
- package/skills/python-testing-patterns/SKILL.md +278 -0
- package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
- package/skills/python-testing-patterns/references/details.md +349 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-review/cloud-infrastructure-security.md +361 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +307 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +344 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/zig-0.16/SKILL.md +840 -0
- package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
- package/src/cli/analyses.ts +280 -0
- package/src/cli/index.ts +108 -0
- package/src/cli/serve.ts +192 -0
- package/src/cli/smoke.ts +131 -0
- package/src/cli/status.test.ts +204 -0
- package/src/cli/status.ts +263 -0
- package/src/cli/vacuum.test.ts +82 -0
- package/src/cli/vacuum.ts +96 -0
- package/src/config/schema.test.ts +88 -0
- package/src/config/schema.ts +64 -0
- package/src/db/analyses-migration.test.ts +210 -0
- package/src/db/analyses.test.ts +466 -0
- package/src/db/analyses.ts +375 -0
- package/src/db/auto-checkpoint.ts +131 -0
- package/src/db/client.test.ts +129 -0
- package/src/db/client.ts +55 -0
- package/src/db/fts-escape.ts +20 -0
- package/src/db/incidents.test.ts +201 -0
- package/src/db/incidents.ts +93 -0
- package/src/db/index.ts +86 -0
- package/src/db/migrations-v13.test.ts +141 -0
- package/src/db/migrations-v8.test.ts +301 -0
- package/src/db/migrations.ts +147 -0
- package/src/db/plan-archive.test.ts +180 -0
- package/src/db/plan-archive.ts +274 -0
- package/src/db/plan-create.test.ts +276 -0
- package/src/db/plan-create.ts +78 -0
- package/src/db/plan-files.test.ts +289 -0
- package/src/db/plan-update-status.ts +287 -0
- package/src/db/plans.test.ts +490 -0
- package/src/db/plans.ts +534 -0
- package/src/db/resolve-project-dir.test.ts +143 -0
- package/src/db/resolve-project-dir.ts +75 -0
- package/src/db/rollbacks.test.ts +150 -0
- package/src/db/rollbacks.ts +67 -0
- package/src/db/schema.ts +907 -0
- package/src/db/sessions.test.ts +80 -0
- package/src/db/sessions.ts +135 -0
- package/src/db/shutdown.test.ts +147 -0
- package/src/db/shutdown.ts +45 -0
- package/src/db/tasks.test.ts +921 -0
- package/src/db/tasks.ts +747 -0
- package/src/db/types.ts +619 -0
- package/src/http/__tests__/auth.test.ts +196 -0
- package/src/http/__tests__/routes.test.ts +465 -0
- package/src/http/__tests__/sse.test.ts +317 -0
- package/src/http/auth.ts +72 -0
- package/src/http/middleware/cors.ts +53 -0
- package/src/http/middleware/security-headers.ts +21 -0
- package/src/http/routes/events.ts +112 -0
- package/src/http/routes/health.ts +51 -0
- package/src/http/routes/plans.ts +66 -0
- package/src/http/routes/sessions.ts +50 -0
- package/src/http/routes/tasks.ts +60 -0
- package/src/http/server.ts +95 -0
- package/src/http/sse.ts +116 -0
- package/src/index.ts +37 -0
- package/src/lib.ts +65 -0
- package/src/mem/scoped.ts +65 -0
- package/src/orchestrator/background.test.ts +268 -0
- package/src/orchestrator/background.ts +293 -0
- package/src/orchestrator/memory-hook.ts +182 -0
- package/src/orchestrator/reconciler.ts +123 -0
- package/src/orchestrator/scheduler.test.ts +300 -0
- package/src/orchestrator/scheduler.ts +243 -0
- package/src/plugin.test.ts +2574 -0
- package/src/plugin.ts +1690 -0
- package/src/sdk/client.ts +66 -0
- package/src/worktrees/manager.ts +236 -0
- package/src/worktrees/state.ts +87 -0
- package/tests/integration/ranger-flow.test.ts +257 -0
- package/tools/analysis_archive.ts +28 -0
- package/tools/analysis_create.ts +55 -0
- package/tools/analysis_get.ts +33 -0
- package/tools/analysis_link_plan.ts +44 -0
- package/tools/analysis_list.ts +48 -0
- package/tools/analysis_search.ts +36 -0
- package/tools/analysis_update.ts +44 -0
- package/tools/plan_approve.ts +31 -0
- package/tools/plan_create.ts +58 -0
- package/tools/plan_get.ts +40 -0
- package/tools/plan_list.ts +37 -0
- package/tools/plan_search.ts +34 -0
- package/tools/plan_update_status.ts +71 -0
- package/tools/session_checkpoint.ts +31 -0
- package/tools/session_end.ts +26 -0
- package/tools/session_start.ts +43 -0
- package/tools/task_create_batch.ts +70 -0
- package/tools/task_list.ts +35 -0
- package/tools/task_next_for_agent.ts +30 -0
- package/tools/task_search.ts +34 -0
- package/tools/task_update_status.ts +37 -0
- package/tsconfig.json +31 -0
package/src/db/schema.ts
ADDED
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ndomo DB — DDL schema definitions and migration registry.
|
|
3
|
+
*
|
|
4
|
+
* Uses IF NOT EXISTS everywhere for idempotency.
|
|
5
|
+
* FTS5 virtual tables are auto-synced via triggers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const SCHEMA_V1_SQL = `
|
|
9
|
+
-- schema_version table
|
|
10
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
11
|
+
version INTEGER PRIMARY KEY,
|
|
12
|
+
applied_at INTEGER NOT NULL,
|
|
13
|
+
description TEXT
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
-- plans table
|
|
17
|
+
CREATE TABLE IF NOT EXISTS plans (
|
|
18
|
+
id TEXT PRIMARY KEY,
|
|
19
|
+
slug TEXT NOT NULL UNIQUE,
|
|
20
|
+
title TEXT NOT NULL,
|
|
21
|
+
status TEXT NOT NULL CHECK(status IN ('draft','approved','executing','completed','failed','abandoned')),
|
|
22
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
23
|
+
created_at INTEGER NOT NULL,
|
|
24
|
+
updated_at INTEGER NOT NULL,
|
|
25
|
+
approved_at INTEGER,
|
|
26
|
+
completed_at INTEGER,
|
|
27
|
+
session_id TEXT,
|
|
28
|
+
overview TEXT NOT NULL,
|
|
29
|
+
approach TEXT,
|
|
30
|
+
complexity INTEGER NOT NULL DEFAULT 3 CHECK(complexity BETWEEN 1 AND 5),
|
|
31
|
+
metadata JSON
|
|
32
|
+
);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_plans_session ON plans(session_id);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_plans_created ON plans(created_at DESC);
|
|
36
|
+
|
|
37
|
+
-- plan_tasks table
|
|
38
|
+
CREATE TABLE IF NOT EXISTS plan_tasks (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
41
|
+
order_index INTEGER NOT NULL,
|
|
42
|
+
description TEXT NOT NULL,
|
|
43
|
+
agent TEXT NOT NULL,
|
|
44
|
+
files JSON NOT NULL DEFAULT '[]',
|
|
45
|
+
complexity INTEGER NOT NULL DEFAULT 3 CHECK(complexity BETWEEN 1 AND 5),
|
|
46
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','running','done','failed','blocked')),
|
|
47
|
+
started_at INTEGER,
|
|
48
|
+
completed_at INTEGER,
|
|
49
|
+
result TEXT,
|
|
50
|
+
error TEXT,
|
|
51
|
+
dependencies JSON DEFAULT '[]',
|
|
52
|
+
metadata JSON,
|
|
53
|
+
UNIQUE(plan_id, order_index)
|
|
54
|
+
);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_plan ON plan_tasks(plan_id);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON plan_tasks(status);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_agent ON plan_tasks(agent);
|
|
58
|
+
|
|
59
|
+
-- sessions table
|
|
60
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
started_at INTEGER NOT NULL,
|
|
63
|
+
ended_at INTEGER,
|
|
64
|
+
last_checkpoint INTEGER,
|
|
65
|
+
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
66
|
+
goal TEXT NOT NULL,
|
|
67
|
+
state JSON NOT NULL DEFAULT '{}',
|
|
68
|
+
agent_history JSON NOT NULL DEFAULT '[]',
|
|
69
|
+
key_decisions TEXT,
|
|
70
|
+
metadata JSON
|
|
71
|
+
);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_plan ON sessions(plan_id);
|
|
74
|
+
|
|
75
|
+
-- FTS5 for plans
|
|
76
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS plans_fts USING fts5(
|
|
77
|
+
id UNINDEXED, title, overview, approach,
|
|
78
|
+
content='plans', content_rowid='rowid', tokenize='porter unicode61'
|
|
79
|
+
);
|
|
80
|
+
CREATE TRIGGER IF NOT EXISTS plans_ai AFTER INSERT ON plans BEGIN
|
|
81
|
+
INSERT INTO plans_fts(rowid, id, title, overview, approach)
|
|
82
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach);
|
|
83
|
+
END;
|
|
84
|
+
CREATE TRIGGER IF NOT EXISTS plans_ad AFTER DELETE ON plans BEGIN
|
|
85
|
+
INSERT INTO plans_fts(plans_fts, rowid, id, title, overview, approach)
|
|
86
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach);
|
|
87
|
+
END;
|
|
88
|
+
CREATE TRIGGER IF NOT EXISTS plans_au AFTER UPDATE ON plans BEGIN
|
|
89
|
+
INSERT INTO plans_fts(plans_fts, rowid, id, title, overview, approach)
|
|
90
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach);
|
|
91
|
+
INSERT INTO plans_fts(rowid, id, title, overview, approach)
|
|
92
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach);
|
|
93
|
+
END;
|
|
94
|
+
|
|
95
|
+
-- FTS5 for plan_tasks
|
|
96
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
97
|
+
id UNINDEXED, description, result, error,
|
|
98
|
+
content='plan_tasks', content_rowid='rowid', tokenize='porter unicode61'
|
|
99
|
+
);
|
|
100
|
+
CREATE TRIGGER IF NOT EXISTS tasks_ai AFTER INSERT ON plan_tasks BEGIN
|
|
101
|
+
INSERT INTO tasks_fts(rowid, id, description, result, error)
|
|
102
|
+
VALUES (new.rowid, new.id, new.description, COALESCE(new.result,''), COALESCE(new.error,''));
|
|
103
|
+
END;
|
|
104
|
+
CREATE TRIGGER IF NOT EXISTS tasks_ad AFTER DELETE ON plan_tasks BEGIN
|
|
105
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, id, description, result, error)
|
|
106
|
+
VALUES ('delete', old.rowid, old.id, old.description, COALESCE(old.result,''), COALESCE(old.error,''));
|
|
107
|
+
END;
|
|
108
|
+
CREATE TRIGGER IF NOT EXISTS tasks_au AFTER UPDATE ON plan_tasks BEGIN
|
|
109
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, id, description, result, error)
|
|
110
|
+
VALUES ('delete', old.rowid, old.id, old.description, COALESCE(old.result,''), COALESCE(old.error,''));
|
|
111
|
+
INSERT INTO tasks_fts(rowid, id, description, result, error)
|
|
112
|
+
VALUES (new.rowid, new.id, new.description, COALESCE(new.result,''), COALESCE(new.error,''));
|
|
113
|
+
END;
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
export const SCHEMA_V2_SQL = `
|
|
117
|
+
-- v2: discriminated metadata + audit + tags
|
|
118
|
+
-- (idempotente: ADD COLUMN con default, IF NOT EXISTS en tablas/triggers)
|
|
119
|
+
|
|
120
|
+
-- plans: audit + source
|
|
121
|
+
ALTER TABLE plans ADD COLUMN created_by TEXT NOT NULL DEFAULT 'unknown';
|
|
122
|
+
ALTER TABLE plans ADD COLUMN updated_by TEXT NOT NULL DEFAULT 'unknown';
|
|
123
|
+
ALTER TABLE plans ADD COLUMN source_session_id TEXT;
|
|
124
|
+
ALTER TABLE plans ADD COLUMN source_message_id TEXT;
|
|
125
|
+
ALTER TABLE plans ADD COLUMN category TEXT CHECK(category IN ('feature','refactor','bugfix','docs','infra'));
|
|
126
|
+
|
|
127
|
+
-- plan_tasks: audit + source + metrics
|
|
128
|
+
ALTER TABLE plan_tasks ADD COLUMN created_by TEXT NOT NULL DEFAULT 'unknown';
|
|
129
|
+
ALTER TABLE plan_tasks ADD COLUMN updated_by TEXT NOT NULL DEFAULT 'unknown';
|
|
130
|
+
ALTER TABLE plan_tasks ADD COLUMN source_session_id TEXT;
|
|
131
|
+
ALTER TABLE plan_tasks ADD COLUMN source_message_id TEXT;
|
|
132
|
+
ALTER TABLE plan_tasks ADD COLUMN reviewed_by TEXT;
|
|
133
|
+
ALTER TABLE plan_tasks ADD COLUMN tokens_used INTEGER;
|
|
134
|
+
ALTER TABLE plan_tasks ADD COLUMN duration_ms INTEGER;
|
|
135
|
+
ALTER TABLE plan_tasks ADD COLUMN artifacts JSON DEFAULT '[]';
|
|
136
|
+
|
|
137
|
+
-- sessions: audit + parent link + outcome
|
|
138
|
+
ALTER TABLE sessions ADD COLUMN created_by TEXT NOT NULL DEFAULT 'unknown';
|
|
139
|
+
ALTER TABLE sessions ADD COLUMN source_message_id TEXT;
|
|
140
|
+
ALTER TABLE sessions ADD COLUMN parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL;
|
|
141
|
+
ALTER TABLE sessions ADD COLUMN outcome TEXT CHECK(outcome IN ('success','partial','failed','abandoned'));
|
|
142
|
+
|
|
143
|
+
-- plan_tags M:N
|
|
144
|
+
CREATE TABLE IF NOT EXISTS plan_tags (
|
|
145
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
146
|
+
tag TEXT NOT NULL,
|
|
147
|
+
added_by TEXT NOT NULL,
|
|
148
|
+
added_at INTEGER NOT NULL,
|
|
149
|
+
PRIMARY KEY (plan_id, tag)
|
|
150
|
+
);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_plan_tags_tag ON plan_tags(tag);
|
|
152
|
+
|
|
153
|
+
-- task_tags M:N
|
|
154
|
+
CREATE TABLE IF NOT EXISTS task_tags (
|
|
155
|
+
task_id TEXT NOT NULL REFERENCES plan_tasks(id) ON DELETE CASCADE,
|
|
156
|
+
tag TEXT NOT NULL,
|
|
157
|
+
added_by TEXT NOT NULL,
|
|
158
|
+
added_at INTEGER NOT NULL,
|
|
159
|
+
PRIMARY KEY (task_id, tag)
|
|
160
|
+
);
|
|
161
|
+
CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag);
|
|
162
|
+
|
|
163
|
+
-- FTS5 ampliado: incluir category de plans y desnormalizar tags via trigger
|
|
164
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS plans_fts_v2 USING fts5(
|
|
165
|
+
id UNINDEXED, title, overview, approach, category, tags,
|
|
166
|
+
content='', tokenize='porter unicode61'
|
|
167
|
+
);
|
|
168
|
+
-- Triggers para mantener plans_fts_v2 sincronizado
|
|
169
|
+
CREATE TRIGGER IF NOT EXISTS plans_v2_ai AFTER INSERT ON plans BEGIN
|
|
170
|
+
INSERT INTO plans_fts_v2(rowid, id, title, overview, approach, category, tags)
|
|
171
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach, new.category, '');
|
|
172
|
+
END;
|
|
173
|
+
CREATE TRIGGER IF NOT EXISTS plans_v2_au AFTER UPDATE ON plans BEGIN
|
|
174
|
+
INSERT INTO plans_fts_v2(plans_fts_v2, rowid, id, title, overview, approach, category, tags)
|
|
175
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach, old.category, '');
|
|
176
|
+
INSERT INTO plans_fts_v2(rowid, id, title, overview, approach, category, tags)
|
|
177
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach, new.category, '');
|
|
178
|
+
END;
|
|
179
|
+
CREATE TRIGGER IF NOT EXISTS plans_v2_ad AFTER DELETE ON plans BEGIN
|
|
180
|
+
INSERT INTO plans_fts_v2(plans_fts_v2, rowid, id, title, overview, approach, category, tags)
|
|
181
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach, old.category, '');
|
|
182
|
+
END;
|
|
183
|
+
`;
|
|
184
|
+
|
|
185
|
+
export const SCHEMA_V3_SQL = `
|
|
186
|
+
-- v3: fix 4 audit observations (1 high, 3 medium)
|
|
187
|
+
-- Fix #9: updated_at auto-trigger (OF columns, no recursion)
|
|
188
|
+
-- Fix #2: metadata DEFAULT '{}' (ALTER COLUMN may fail on older SQLite — mapper fallback exists)
|
|
189
|
+
-- Fix #14: composite indexes for common query patterns
|
|
190
|
+
-- Fix #1 + #8: session_id FK validated at app level (see plans.ts)
|
|
191
|
+
|
|
192
|
+
-- Fix #2: metadata DEFAULT '{}' — ALTER COLUMN SET DEFAULT is NOT supported
|
|
193
|
+
-- in bun:sqlite 1.3.14 (SQLite 3.45.1). The mapper in types.ts already handles
|
|
194
|
+
-- null/undefined metadata with ?? '{}' so the data layer is safe either way.
|
|
195
|
+
-- If a future bun version supports this, uncomment these lines:
|
|
196
|
+
-- ALTER TABLE plans ALTER COLUMN metadata SET DEFAULT '{}';
|
|
197
|
+
-- ALTER TABLE plan_tasks ALTER COLUMN metadata SET DEFAULT '{}';
|
|
198
|
+
-- ALTER TABLE sessions ALTER COLUMN metadata SET DEFAULT '{}';
|
|
199
|
+
|
|
200
|
+
-- Fix #9: updated_at auto-update via BEFORE UPDATE OF <column_list>.
|
|
201
|
+
-- The OF clause lists only "editable" columns, excluding updated_at itself
|
|
202
|
+
-- and immutable columns (id, created_at). This guarantees no recursion loop.
|
|
203
|
+
-- Sessions use last_checkpoint instead of updated_at (semantic: each UPDATE = checkpoint).
|
|
204
|
+
CREATE TRIGGER IF NOT EXISTS trg_plans_updated_at_bu
|
|
205
|
+
BEFORE UPDATE OF title, status, priority, overview, approach, complexity, category, updated_by, metadata ON plans
|
|
206
|
+
BEGIN
|
|
207
|
+
UPDATE plans SET updated_at = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)
|
|
208
|
+
WHERE rowid = NEW.rowid;
|
|
209
|
+
END;
|
|
210
|
+
|
|
211
|
+
CREATE TRIGGER IF NOT EXISTS trg_tasks_updated_at_bu
|
|
212
|
+
BEFORE UPDATE OF description, agent, files, complexity, status, updated_by, artifacts, metadata ON plan_tasks
|
|
213
|
+
BEGIN
|
|
214
|
+
UPDATE plan_tasks SET updated_at = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)
|
|
215
|
+
WHERE rowid = NEW.rowid;
|
|
216
|
+
END;
|
|
217
|
+
|
|
218
|
+
CREATE TRIGGER IF NOT EXISTS trg_sessions_updated_at_bu
|
|
219
|
+
BEFORE UPDATE OF goal, state, agent_history, key_decisions, outcome, ended_at, metadata ON sessions
|
|
220
|
+
BEGIN
|
|
221
|
+
UPDATE sessions SET last_checkpoint = CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)
|
|
222
|
+
WHERE rowid = NEW.rowid;
|
|
223
|
+
END;
|
|
224
|
+
|
|
225
|
+
-- Fix #14: composite indexes for common query patterns
|
|
226
|
+
CREATE INDEX IF NOT EXISTS idx_plans_status_priority ON plans(status, priority DESC);
|
|
227
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_plan_status ON plan_tasks(plan_id, status);
|
|
228
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_agent_status ON plan_tasks(agent, status);
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
export const SCHEMA_V4_SQL = `
|
|
232
|
+
-- v4: 5 schema fixes + 1 code fix (6 low-priority observations)
|
|
233
|
+
|
|
234
|
+
-- ── Fix 1: plans.priority CHECK(1-4) via trigger ────────────────────────────
|
|
235
|
+
-- SQLite lacks ALTER TABLE ADD CONSTRAINT; use BEFORE INSERT/UPDATE trigger.
|
|
236
|
+
|
|
237
|
+
-- ── Pre-fix: drop broken v3 trigger (references non-existent updated_at on plan_tasks)
|
|
238
|
+
DROP TRIGGER IF EXISTS trg_tasks_updated_at_bu;
|
|
239
|
+
|
|
240
|
+
DROP TRIGGER IF EXISTS trg_plans_priority_check;
|
|
241
|
+
CREATE TRIGGER trg_plans_priority_check
|
|
242
|
+
BEFORE INSERT ON plans
|
|
243
|
+
BEGIN
|
|
244
|
+
SELECT RAISE(ABORT, 'ndomo: priority must be 1-4')
|
|
245
|
+
WHERE NEW.priority NOT BETWEEN 1 AND 4;
|
|
246
|
+
END;
|
|
247
|
+
|
|
248
|
+
DROP TRIGGER IF EXISTS trg_plans_priority_check_u;
|
|
249
|
+
CREATE TRIGGER trg_plans_priority_check_u
|
|
250
|
+
BEFORE UPDATE OF priority ON plans
|
|
251
|
+
BEGIN
|
|
252
|
+
SELECT RAISE(ABORT, 'ndomo: priority must be 1-4')
|
|
253
|
+
WHERE NEW.priority NOT BETWEEN 1 AND 4;
|
|
254
|
+
END;
|
|
255
|
+
|
|
256
|
+
-- ── Fix 2: plans.slug format validation (kebab-case) ────────────────────────
|
|
257
|
+
DROP TRIGGER IF EXISTS trg_plans_slug_check;
|
|
258
|
+
CREATE TRIGGER trg_plans_slug_check
|
|
259
|
+
BEFORE INSERT ON plans
|
|
260
|
+
BEGIN
|
|
261
|
+
SELECT RAISE(ABORT, 'ndomo: slug must be kebab-case 1-60 chars [a-z0-9]+(-[a-z0-9]+)*')
|
|
262
|
+
WHERE NEW.slug NOT GLOB '[a-z0-9]*'
|
|
263
|
+
OR NEW.slug GLOB '*--*'
|
|
264
|
+
OR NEW.slug GLOB '*-'
|
|
265
|
+
OR length(NEW.slug) = 0
|
|
266
|
+
OR length(NEW.slug) > 60
|
|
267
|
+
OR NEW.slug GLOB '*[^a-z0-9-]*';
|
|
268
|
+
END;
|
|
269
|
+
|
|
270
|
+
DROP TRIGGER IF EXISTS trg_plans_slug_check_u;
|
|
271
|
+
CREATE TRIGGER trg_plans_slug_check_u
|
|
272
|
+
BEFORE UPDATE OF slug ON plans
|
|
273
|
+
BEGIN
|
|
274
|
+
SELECT RAISE(ABORT, 'ndomo: slug must be kebab-case 1-60 chars [a-z0-9]+(-[a-z0-9]+)*')
|
|
275
|
+
WHERE NEW.slug NOT GLOB '[a-z0-9]*'
|
|
276
|
+
OR NEW.slug GLOB '*--*'
|
|
277
|
+
OR NEW.slug GLOB '*-'
|
|
278
|
+
OR length(NEW.slug) = 0
|
|
279
|
+
OR length(NEW.slug) > 60
|
|
280
|
+
OR NEW.slug GLOB '*[^a-z0-9-]*';
|
|
281
|
+
END;
|
|
282
|
+
|
|
283
|
+
-- ── Fix 3: plan_progress view ───────────────────────────────────────────────
|
|
284
|
+
DROP VIEW IF EXISTS plan_progress;
|
|
285
|
+
CREATE VIEW plan_progress AS
|
|
286
|
+
SELECT
|
|
287
|
+
p.id AS plan_id,
|
|
288
|
+
p.slug,
|
|
289
|
+
p.title,
|
|
290
|
+
p.status,
|
|
291
|
+
COUNT(t.id) AS total_tasks,
|
|
292
|
+
SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) AS done,
|
|
293
|
+
SUM(CASE WHEN t.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
294
|
+
SUM(CASE WHEN t.status = 'running' THEN 1 ELSE 0 END) AS running,
|
|
295
|
+
SUM(CASE WHEN t.status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
296
|
+
SUM(CASE WHEN t.status = 'blocked' THEN 1 ELSE 0 END) AS blocked,
|
|
297
|
+
CASE
|
|
298
|
+
WHEN COUNT(t.id) = 0 THEN 0
|
|
299
|
+
ELSE ROUND(SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) * 100.0 / COUNT(t.id))
|
|
300
|
+
END AS progress_pct
|
|
301
|
+
FROM plans p
|
|
302
|
+
LEFT JOIN plan_tasks t ON t.plan_id = p.id
|
|
303
|
+
GROUP BY p.id;
|
|
304
|
+
|
|
305
|
+
-- ── Fix 4: FTS5 — unicode61 remove_diacritics 1 + spanish stopwords ────────
|
|
306
|
+
-- Drop legacy v1 FTS (content-synced, superseded by v2)
|
|
307
|
+
DROP TRIGGER IF EXISTS plans_ai;
|
|
308
|
+
DROP TRIGGER IF EXISTS plans_ad;
|
|
309
|
+
DROP TRIGGER IF EXISTS plans_au;
|
|
310
|
+
DROP TABLE IF EXISTS plans_fts;
|
|
311
|
+
|
|
312
|
+
-- Drop tasks_fts + sync triggers (v1, will recreate with new tokenizer)
|
|
313
|
+
DROP TRIGGER IF EXISTS tasks_ai;
|
|
314
|
+
DROP TRIGGER IF EXISTS tasks_ad;
|
|
315
|
+
DROP TRIGGER IF EXISTS tasks_au;
|
|
316
|
+
DROP TABLE IF EXISTS tasks_fts;
|
|
317
|
+
|
|
318
|
+
-- Drop plans_fts_v2 + sync triggers (v2, will recreate with new tokenizer)
|
|
319
|
+
DROP TRIGGER IF EXISTS plans_v2_ai;
|
|
320
|
+
DROP TRIGGER IF EXISTS plans_v2_au;
|
|
321
|
+
DROP TRIGGER IF EXISTS plans_v2_ad;
|
|
322
|
+
DROP TABLE IF EXISTS plans_fts_v2;
|
|
323
|
+
|
|
324
|
+
-- Recreate plans_fts_v2: unicode61 with diacritics removal (no Porter stemmer)
|
|
325
|
+
CREATE VIRTUAL TABLE plans_fts_v2 USING fts5(
|
|
326
|
+
id UNINDEXED, title, overview, approach, category, tags,
|
|
327
|
+
content='', tokenize='unicode61 remove_diacritics 1'
|
|
328
|
+
);
|
|
329
|
+
CREATE TRIGGER plans_v2_ai AFTER INSERT ON plans BEGIN
|
|
330
|
+
INSERT INTO plans_fts_v2(rowid, id, title, overview, approach, category, tags)
|
|
331
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach, new.category, '');
|
|
332
|
+
END;
|
|
333
|
+
CREATE TRIGGER plans_v2_ad AFTER DELETE ON plans BEGIN
|
|
334
|
+
INSERT INTO plans_fts_v2(plans_fts_v2, rowid, id, title, overview, approach, category, tags)
|
|
335
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach, old.category, '');
|
|
336
|
+
END;
|
|
337
|
+
CREATE TRIGGER plans_v2_au AFTER UPDATE ON plans BEGIN
|
|
338
|
+
INSERT INTO plans_fts_v2(plans_fts_v2, rowid, id, title, overview, approach, category, tags)
|
|
339
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach, old.category, '');
|
|
340
|
+
INSERT INTO plans_fts_v2(rowid, id, title, overview, approach, category, tags)
|
|
341
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach, new.category, '');
|
|
342
|
+
END;
|
|
343
|
+
|
|
344
|
+
-- Recreate tasks_fts: unicode61 with diacritics removal (no Porter stemmer)
|
|
345
|
+
CREATE VIRTUAL TABLE tasks_fts USING fts5(
|
|
346
|
+
id UNINDEXED, description, result, error,
|
|
347
|
+
content='plan_tasks', content_rowid='rowid',
|
|
348
|
+
tokenize='unicode61 remove_diacritics 1'
|
|
349
|
+
);
|
|
350
|
+
CREATE TRIGGER tasks_ai AFTER INSERT ON plan_tasks BEGIN
|
|
351
|
+
INSERT INTO tasks_fts(rowid, id, description, result, error)
|
|
352
|
+
VALUES (new.rowid, new.id, new.description, COALESCE(new.result, ''), COALESCE(new.error, ''));
|
|
353
|
+
END;
|
|
354
|
+
CREATE TRIGGER tasks_ad AFTER DELETE ON plan_tasks BEGIN
|
|
355
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, id, description, result, error)
|
|
356
|
+
VALUES ('delete', old.rowid, old.id, old.description, COALESCE(old.result, ''), COALESCE(old.error, ''));
|
|
357
|
+
END;
|
|
358
|
+
CREATE TRIGGER tasks_au AFTER UPDATE ON plan_tasks BEGIN
|
|
359
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, id, description, result, error)
|
|
360
|
+
VALUES ('delete', old.rowid, old.id, old.description, COALESCE(old.result, ''), COALESCE(old.error, ''));
|
|
361
|
+
INSERT INTO tasks_fts(rowid, id, description, result, error)
|
|
362
|
+
VALUES (new.rowid, new.id, new.description, COALESCE(new.result, ''), COALESCE(new.error, ''));
|
|
363
|
+
END;
|
|
364
|
+
|
|
365
|
+
-- Rebuild FTS indexes from content tables
|
|
366
|
+
INSERT INTO plans_fts_v2(plans_fts_v2) VALUES ('rebuild');
|
|
367
|
+
INSERT INTO tasks_fts(tasks_fts) VALUES ('rebuild');
|
|
368
|
+
|
|
369
|
+
-- Spanish stopwords (common function words, not indexed by FTS)
|
|
370
|
+
-- NOTE: fts5_stopwords table is populated but NOT referenced by the
|
|
371
|
+
-- unicode61 tokenizer (which doesn't support custom stopwords tables).
|
|
372
|
+
-- Table is kept for future use if a custom tokenizer is added. See FINDING-2.
|
|
373
|
+
CREATE TABLE IF NOT EXISTS fts5_stopwords(value TEXT PRIMARY KEY);
|
|
374
|
+
INSERT OR IGNORE INTO fts5_stopwords(value) VALUES
|
|
375
|
+
('el'),('la'),('de'),('que'),('y'),('a'),('en'),('un'),('ser'),('se'),
|
|
376
|
+
('no'),('haber'),('por'),('con'),('su'),('para'),('como'),('estar'),
|
|
377
|
+
('tener'),('le'),('lo'),('todo'),('pero'),('más'),('hacer'),('sobre'),
|
|
378
|
+
('sin'),('año'),('día'),('vez'),('sí'),('porque'),('esta'),('entre'),
|
|
379
|
+
('cuando'),('muy'),('tras'),('hasta'),('donde'),('desde'),('todos'),
|
|
380
|
+
('también'),('otro'),('ese'),('eso'),('ante'),('ellos'),
|
|
381
|
+
('e'),('esto'),('antes'),('algunos'),('qué'),('unos'),('yo'),('otra'),
|
|
382
|
+
('otras'),('otros'),('cual'),('si'),('mi'),('tú'),('te');
|
|
383
|
+
|
|
384
|
+
-- ── Fix 6: plan_tasks.metadata DEFAULT '{}' via trigger ─────────────────────
|
|
385
|
+
-- BEFORE triggers cannot modify NEW in SQLite. AFTER INSERT is safe because
|
|
386
|
+
-- createTasksBatch already passes metadata=??{}, so this is purely defensive
|
|
387
|
+
-- for direct SQL inserts or future callers that omit metadata.
|
|
388
|
+
DROP TRIGGER IF EXISTS trg_tasks_metadata_default;
|
|
389
|
+
CREATE TRIGGER trg_tasks_metadata_default
|
|
390
|
+
AFTER INSERT ON plan_tasks
|
|
391
|
+
WHEN NEW.metadata IS NULL
|
|
392
|
+
BEGIN
|
|
393
|
+
UPDATE plan_tasks SET metadata = '{}' WHERE rowid = NEW.rowid;
|
|
394
|
+
END;
|
|
395
|
+
`;
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* v5: soft delete via archived_at + composite indexes.
|
|
399
|
+
*
|
|
400
|
+
* The ALTER TABLE ADD COLUMN statements are executed by migrations.ts
|
|
401
|
+
* (addColumnIfMissing) because SQLite 3.45 lacks IF NOT EXISTS for columns.
|
|
402
|
+
* This SQL only contains the CREATE INDEX statements which are idempotent.
|
|
403
|
+
*/
|
|
404
|
+
export const SCHEMA_V5_SQL = `
|
|
405
|
+
-- v5: soft delete — archived_at columns + indexes
|
|
406
|
+
-- Columns added via addColumnIfMissing() in migrations.ts (idempotent ALTER TABLE)
|
|
407
|
+
CREATE INDEX IF NOT EXISTS idx_plans_archived ON plans(archived_at);
|
|
408
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_archived ON plan_tasks(archived_at);
|
|
409
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_archived ON sessions(archived_at);
|
|
410
|
+
|
|
411
|
+
-- Fix: plan_progress view must exclude archived plans AND archived tasks from counts
|
|
412
|
+
DROP VIEW IF EXISTS plan_progress;
|
|
413
|
+
CREATE VIEW plan_progress AS
|
|
414
|
+
SELECT
|
|
415
|
+
p.id AS plan_id,
|
|
416
|
+
p.slug,
|
|
417
|
+
p.title,
|
|
418
|
+
p.status,
|
|
419
|
+
COUNT(t.id) AS total_tasks,
|
|
420
|
+
SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) AS done,
|
|
421
|
+
SUM(CASE WHEN t.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
422
|
+
SUM(CASE WHEN t.status = 'running' THEN 1 ELSE 0 END) AS running,
|
|
423
|
+
SUM(CASE WHEN t.status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
424
|
+
SUM(CASE WHEN t.status = 'blocked' THEN 1 ELSE 0 END) AS blocked,
|
|
425
|
+
CASE
|
|
426
|
+
WHEN COUNT(t.id) = 0 THEN 0
|
|
427
|
+
ELSE ROUND(SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) * 100.0 / COUNT(t.id))
|
|
428
|
+
END AS progress_pct
|
|
429
|
+
FROM plans p
|
|
430
|
+
LEFT JOIN plan_tasks t ON t.plan_id = p.id AND t.archived_at IS NULL
|
|
431
|
+
WHERE p.archived_at IS NULL
|
|
432
|
+
GROUP BY p.id;
|
|
433
|
+
|
|
434
|
+
-- Fix: plans_fts_v2 was contentless (content='') — columns return NULL on SELECT,
|
|
435
|
+
-- breaking searchPlans JOIN/subquery. Switch to external content (content='plans')
|
|
436
|
+
-- so column values are readable via the content table.
|
|
437
|
+
DROP TRIGGER IF EXISTS plans_v2_ai;
|
|
438
|
+
DROP TRIGGER IF EXISTS plans_v2_ad;
|
|
439
|
+
DROP TRIGGER IF EXISTS plans_v2_au;
|
|
440
|
+
DROP TABLE IF EXISTS plans_fts_v2;
|
|
441
|
+
|
|
442
|
+
CREATE VIRTUAL TABLE plans_fts_v2 USING fts5(
|
|
443
|
+
id UNINDEXED, title, overview, approach, category,
|
|
444
|
+
content='plans', content_rowid='rowid',
|
|
445
|
+
tokenize='unicode61 remove_diacritics 1'
|
|
446
|
+
);
|
|
447
|
+
CREATE TRIGGER plans_v2_ai AFTER INSERT ON plans BEGIN
|
|
448
|
+
INSERT INTO plans_fts_v2(rowid, id, title, overview, approach, category)
|
|
449
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach, new.category);
|
|
450
|
+
END;
|
|
451
|
+
CREATE TRIGGER plans_v2_ad AFTER DELETE ON plans BEGIN
|
|
452
|
+
INSERT INTO plans_fts_v2(plans_fts_v2, rowid, id, title, overview, approach, category)
|
|
453
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach, old.category);
|
|
454
|
+
END;
|
|
455
|
+
CREATE TRIGGER plans_v2_au AFTER UPDATE ON plans BEGIN
|
|
456
|
+
INSERT INTO plans_fts_v2(plans_fts_v2, rowid, id, title, overview, approach, category)
|
|
457
|
+
VALUES ('delete', old.rowid, old.id, old.title, old.overview, old.approach, old.category);
|
|
458
|
+
INSERT INTO plans_fts_v2(rowid, id, title, overview, approach, category)
|
|
459
|
+
VALUES (new.rowid, new.id, new.title, new.overview, new.approach, new.category);
|
|
460
|
+
END;
|
|
461
|
+
|
|
462
|
+
-- Rebuild FTS index from content table
|
|
463
|
+
INSERT INTO plans_fts_v2(plans_fts_v2) VALUES ('rebuild');
|
|
464
|
+
`;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* v6: write-once audit trail — original_plan_data on plans + plan_tasks.
|
|
468
|
+
*
|
|
469
|
+
* Columns added via addColumnIfMissing() in migrations.ts (idempotent ALTER TABLE).
|
|
470
|
+
* This SQL is intentionally empty — all DDL is in migrations.ts for this version.
|
|
471
|
+
*/
|
|
472
|
+
export const SCHEMA_V6_SQL =
|
|
473
|
+
"-- v6: original_plan_data columns added via addColumnIfMissing in migrations.ts";
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* v7: plan_files join table — M:N relationship between plans and files.
|
|
477
|
+
*
|
|
478
|
+
* Tracks which files are associated with a plan and their role (e.g., 'input', 'output', 'reference').
|
|
479
|
+
*/
|
|
480
|
+
export const SCHEMA_V7_SQL = `
|
|
481
|
+
CREATE TABLE IF NOT EXISTS plan_files (
|
|
482
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
483
|
+
file_path TEXT NOT NULL,
|
|
484
|
+
role TEXT NOT NULL DEFAULT 'input',
|
|
485
|
+
PRIMARY KEY (plan_id, file_path)
|
|
486
|
+
);
|
|
487
|
+
CREATE INDEX IF NOT EXISTS idx_plan_files_plan ON plan_files(plan_id);
|
|
488
|
+
`;
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* v8: agent execution tracking — created_by_agent, executed_by_agent, executed_by_session.
|
|
492
|
+
*
|
|
493
|
+
* Columns added via addColumnIfMissing() in migrations.ts (idempotent ALTER TABLE).
|
|
494
|
+
* executed_by_session is a FK to sessions(id) validated at app level.
|
|
495
|
+
* This SQL is intentionally empty — all DDL is in migrations.ts for this version.
|
|
496
|
+
*/
|
|
497
|
+
export const SCHEMA_V8_SQL =
|
|
498
|
+
"-- v8: agent tracking columns added via addColumnIfMissing in migrations.ts";
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* v9: plan_progress view fix — exclude archived plans.
|
|
502
|
+
*
|
|
503
|
+
* DBs that already had schema_version >= 5 when the fix landed in v5
|
|
504
|
+
* never re-ran v5, so they still have the old view without archived_at filters.
|
|
505
|
+
* This migration recreates the view unconditionally (DROP + CREATE).
|
|
506
|
+
*/
|
|
507
|
+
export const SCHEMA_V9_SQL = `
|
|
508
|
+
DROP VIEW IF EXISTS plan_progress;
|
|
509
|
+
CREATE VIEW plan_progress AS
|
|
510
|
+
SELECT
|
|
511
|
+
p.id AS plan_id,
|
|
512
|
+
p.slug,
|
|
513
|
+
p.title,
|
|
514
|
+
p.status,
|
|
515
|
+
COUNT(t.id) AS total_tasks,
|
|
516
|
+
SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) AS done,
|
|
517
|
+
SUM(CASE WHEN t.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
518
|
+
SUM(CASE WHEN t.status = 'running' THEN 1 ELSE 0 END) AS running,
|
|
519
|
+
SUM(CASE WHEN t.status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
520
|
+
SUM(CASE WHEN t.status = 'blocked' THEN 1 ELSE 0 END) AS blocked,
|
|
521
|
+
CASE
|
|
522
|
+
WHEN COUNT(t.id) = 0 THEN 0
|
|
523
|
+
ELSE ROUND(SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) * 100.0 / COUNT(t.id))
|
|
524
|
+
END AS progress_pct
|
|
525
|
+
FROM plans p
|
|
526
|
+
LEFT JOIN plan_tasks t ON t.plan_id = p.id AND t.archived_at IS NULL
|
|
527
|
+
WHERE p.archived_at IS NULL
|
|
528
|
+
GROUP BY p.id;
|
|
529
|
+
`;
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* v10: plan_files multi-role PK + created_at.
|
|
533
|
+
*
|
|
534
|
+
* Changes PK from (plan_id, file_path) to (plan_id, file_path, role)
|
|
535
|
+
* so the same file can have multiple roles (input + modified + reviewed).
|
|
536
|
+
* Also adds created_at column for ordering/auditing.
|
|
537
|
+
*
|
|
538
|
+
* Safe migration: recreate table (SQLite cannot ALTER PK directly).
|
|
539
|
+
*/
|
|
540
|
+
export const SCHEMA_V10_SQL = `
|
|
541
|
+
-- v10: plan_files multi-role PK + created_at
|
|
542
|
+
CREATE TABLE IF NOT EXISTS plan_files_new (
|
|
543
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
544
|
+
file_path TEXT NOT NULL,
|
|
545
|
+
role TEXT NOT NULL DEFAULT 'input',
|
|
546
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),
|
|
547
|
+
PRIMARY KEY (plan_id, file_path, role)
|
|
548
|
+
);
|
|
549
|
+
INSERT INTO plan_files_new (plan_id, file_path, role, created_at)
|
|
550
|
+
SELECT plan_id, file_path, role, strftime('%s','now') * 1000 FROM plan_files;
|
|
551
|
+
DROP TABLE plan_files;
|
|
552
|
+
ALTER TABLE plan_files_new RENAME TO plan_files;
|
|
553
|
+
CREATE INDEX IF NOT EXISTS idx_plan_files_plan ON plan_files(plan_id);
|
|
554
|
+
`;
|
|
555
|
+
|
|
556
|
+
export const SCHEMA_V11_SQL = `
|
|
557
|
+
CREATE TABLE IF NOT EXISTS background_tasks (
|
|
558
|
+
id TEXT PRIMARY KEY,
|
|
559
|
+
agent TEXT NOT NULL,
|
|
560
|
+
description TEXT NOT NULL,
|
|
561
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','running','completed','failed','cancelled')),
|
|
562
|
+
session_id TEXT,
|
|
563
|
+
result TEXT,
|
|
564
|
+
started_at INTEGER,
|
|
565
|
+
completed_at INTEGER,
|
|
566
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),
|
|
567
|
+
files TEXT,
|
|
568
|
+
worktree TEXT
|
|
569
|
+
);
|
|
570
|
+
CREATE INDEX IF NOT EXISTS idx_background_tasks_status ON background_tasks(status);
|
|
571
|
+
CREATE INDEX IF NOT EXISTS idx_background_tasks_agent ON background_tasks(agent);
|
|
572
|
+
`;
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* v12: DB optimization — plan_progress views, composite indexes, plan_audit skeleton.
|
|
576
|
+
*
|
|
577
|
+
* (1) plan_progress_active + plan_progress_historical views (P1 fix)
|
|
578
|
+
* (2) Replace idx_plans_status_priority with idx_plans_listplans (P1.5)
|
|
579
|
+
* (3) Consolidate plan_tasks indexes — drop redundant single-column indexes (P3)
|
|
580
|
+
* (4) Add composite indexes on background_tasks (P2)
|
|
581
|
+
* (5) plan_audit table skeleton for future audit trail (P4 foundation)
|
|
582
|
+
*/
|
|
583
|
+
export const SCHEMA_V12_SQL = `
|
|
584
|
+
-- v12: plan_progress views (explicit active + historical) + index optimization + plan_audit skeleton
|
|
585
|
+
|
|
586
|
+
-- P1: plan_progress_active (explicit name, same as current plan_progress)
|
|
587
|
+
DROP VIEW IF EXISTS plan_progress;
|
|
588
|
+
DROP VIEW IF EXISTS plan_progress_active;
|
|
589
|
+
DROP VIEW IF EXISTS plan_progress_historical;
|
|
590
|
+
|
|
591
|
+
CREATE VIEW plan_progress_active AS
|
|
592
|
+
SELECT
|
|
593
|
+
p.id AS plan_id,
|
|
594
|
+
p.slug,
|
|
595
|
+
p.title,
|
|
596
|
+
p.status,
|
|
597
|
+
COUNT(t.id) AS total_tasks,
|
|
598
|
+
SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) AS done,
|
|
599
|
+
SUM(CASE WHEN t.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
600
|
+
SUM(CASE WHEN t.status = 'running' THEN 1 ELSE 0 END) AS running,
|
|
601
|
+
SUM(CASE WHEN t.status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
602
|
+
SUM(CASE WHEN t.status = 'blocked' THEN 1 ELSE 0 END) AS blocked,
|
|
603
|
+
CASE
|
|
604
|
+
WHEN COUNT(t.id) = 0 THEN 0
|
|
605
|
+
ELSE ROUND(SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) * 100.0 / COUNT(t.id))
|
|
606
|
+
END AS progress_pct
|
|
607
|
+
FROM plans p
|
|
608
|
+
LEFT JOIN plan_tasks t ON t.plan_id = p.id AND t.archived_at IS NULL
|
|
609
|
+
WHERE p.archived_at IS NULL
|
|
610
|
+
GROUP BY p.id;
|
|
611
|
+
|
|
612
|
+
-- P1: plan_progress_historical (ALL plans, ALL tasks)
|
|
613
|
+
CREATE VIEW plan_progress_historical AS
|
|
614
|
+
SELECT
|
|
615
|
+
p.id AS plan_id,
|
|
616
|
+
p.slug,
|
|
617
|
+
p.title,
|
|
618
|
+
p.status,
|
|
619
|
+
COUNT(t.id) AS total_tasks,
|
|
620
|
+
SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) AS done,
|
|
621
|
+
SUM(CASE WHEN t.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
622
|
+
SUM(CASE WHEN t.status = 'running' THEN 1 ELSE 0 END) AS running,
|
|
623
|
+
SUM(CASE WHEN t.status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
624
|
+
SUM(CASE WHEN t.status = 'blocked' THEN 1 ELSE 0 END) AS blocked,
|
|
625
|
+
CASE
|
|
626
|
+
WHEN COUNT(t.id) = 0 THEN 0
|
|
627
|
+
ELSE ROUND(SUM(CASE WHEN t.status = 'done' THEN 1 ELSE 0 END) * 100.0 / COUNT(t.id))
|
|
628
|
+
END AS progress_pct
|
|
629
|
+
FROM plans p
|
|
630
|
+
LEFT JOIN plan_tasks t ON t.plan_id = p.id
|
|
631
|
+
GROUP BY p.id;
|
|
632
|
+
|
|
633
|
+
-- Backward compat: plan_progress = plan_progress_active
|
|
634
|
+
CREATE VIEW plan_progress AS
|
|
635
|
+
SELECT * FROM plan_progress_active;
|
|
636
|
+
|
|
637
|
+
-- P1.5: replace idx_plans_status_priority with composite matching listPlans query
|
|
638
|
+
DROP INDEX IF EXISTS idx_plans_status_priority;
|
|
639
|
+
CREATE INDEX IF NOT EXISTS idx_plans_listplans ON plans(status, archived_at, priority, created_at DESC);
|
|
640
|
+
|
|
641
|
+
-- P3: consolidate plan_tasks indexes — drop redundant single-column indexes
|
|
642
|
+
DROP INDEX IF EXISTS idx_tasks_status;
|
|
643
|
+
DROP INDEX IF EXISTS idx_tasks_agent;
|
|
644
|
+
DROP INDEX IF EXISTS idx_tasks_archived;
|
|
645
|
+
|
|
646
|
+
-- P2: background_tasks composite indexes for common query patterns
|
|
647
|
+
CREATE INDEX IF NOT EXISTS idx_background_tasks_agent_created ON background_tasks(agent, created_at DESC);
|
|
648
|
+
CREATE INDEX IF NOT EXISTS idx_background_tasks_created ON background_tasks(created_at DESC);
|
|
649
|
+
|
|
650
|
+
-- P4 foundation: plan_audit table skeleton
|
|
651
|
+
CREATE TABLE IF NOT EXISTS plan_audit (
|
|
652
|
+
plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
|
|
653
|
+
captured_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),
|
|
654
|
+
snapshot TEXT NOT NULL,
|
|
655
|
+
trigger TEXT NOT NULL DEFAULT 'archive',
|
|
656
|
+
PRIMARY KEY(plan_id, captured_at)
|
|
657
|
+
);
|
|
658
|
+
CREATE INDEX IF NOT EXISTS idx_plan_audit_captured ON plan_audit(captured_at DESC);
|
|
659
|
+
`;
|
|
660
|
+
|
|
661
|
+
// ── v13: ops tables ─────────────────────────────────────────────────────────
|
|
662
|
+
|
|
663
|
+
export const SCHEMA_V13_SQL = `
|
|
664
|
+
-- v13: ops tables (environments, releases, deployments, incidents, rollback_executions)
|
|
665
|
+
|
|
666
|
+
-- environments: named deployment targets (e.g. "prod", "staging", "dev")
|
|
667
|
+
CREATE TABLE IF NOT EXISTS environments (
|
|
668
|
+
id TEXT PRIMARY KEY,
|
|
669
|
+
name TEXT NOT NULL UNIQUE,
|
|
670
|
+
slug TEXT NOT NULL UNIQUE,
|
|
671
|
+
description TEXT,
|
|
672
|
+
metadata JSON,
|
|
673
|
+
created_at INTEGER NOT NULL,
|
|
674
|
+
updated_at INTEGER NOT NULL,
|
|
675
|
+
archived_at INTEGER
|
|
676
|
+
);
|
|
677
|
+
CREATE INDEX IF NOT EXISTS idx_environments_slug ON environments(slug);
|
|
678
|
+
CREATE INDEX IF NOT EXISTS idx_environments_archived ON environments(archived_at);
|
|
679
|
+
|
|
680
|
+
-- releases: versioned artifacts that can be deployed
|
|
681
|
+
CREATE TABLE IF NOT EXISTS releases (
|
|
682
|
+
id TEXT PRIMARY KEY,
|
|
683
|
+
version TEXT NOT NULL,
|
|
684
|
+
title TEXT NOT NULL,
|
|
685
|
+
notes TEXT,
|
|
686
|
+
metadata JSON,
|
|
687
|
+
created_at INTEGER NOT NULL,
|
|
688
|
+
archived_at INTEGER,
|
|
689
|
+
CHECK(version <> '')
|
|
690
|
+
);
|
|
691
|
+
CREATE INDEX IF NOT EXISTS idx_releases_version ON releases(version);
|
|
692
|
+
CREATE INDEX IF NOT EXISTS idx_releases_created ON releases(created_at DESC);
|
|
693
|
+
CREATE INDEX IF NOT EXISTS idx_releases_archived ON releases(archived_at);
|
|
694
|
+
|
|
695
|
+
-- deployments: a release deployed to an environment at a point in time
|
|
696
|
+
CREATE TABLE IF NOT EXISTS deployments (
|
|
697
|
+
id TEXT PRIMARY KEY,
|
|
698
|
+
release_id TEXT NOT NULL REFERENCES releases(id) ON DELETE RESTRICT,
|
|
699
|
+
environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE RESTRICT,
|
|
700
|
+
status TEXT NOT NULL DEFAULT 'planned' CHECK(status IN ('planned','in_progress','succeeded','failed','rolled_back')),
|
|
701
|
+
deployed_at INTEGER,
|
|
702
|
+
created_at INTEGER NOT NULL,
|
|
703
|
+
metadata JSON,
|
|
704
|
+
CHECK(release_id <> ''),
|
|
705
|
+
CHECK(environment_id <> '')
|
|
706
|
+
);
|
|
707
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_release ON deployments(release_id);
|
|
708
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_env ON deployments(environment_id);
|
|
709
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_status ON deployments(status);
|
|
710
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_deployed ON deployments(deployed_at DESC);
|
|
711
|
+
|
|
712
|
+
-- incidents: operational events, optionally linked to a triggering deployment
|
|
713
|
+
CREATE TABLE IF NOT EXISTS incidents (
|
|
714
|
+
id TEXT PRIMARY KEY,
|
|
715
|
+
title TEXT NOT NULL CHECK(title <> ''),
|
|
716
|
+
severity TEXT NOT NULL CHECK(severity IN ('sev1','sev2','sev3','sev4')),
|
|
717
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','triaging','mitigated','resolved','postmortem')),
|
|
718
|
+
summary TEXT,
|
|
719
|
+
triggered_by_deployment_id TEXT REFERENCES deployments(id) ON DELETE SET NULL,
|
|
720
|
+
created_at INTEGER NOT NULL,
|
|
721
|
+
updated_at INTEGER NOT NULL,
|
|
722
|
+
resolved_at INTEGER,
|
|
723
|
+
metadata JSON
|
|
724
|
+
);
|
|
725
|
+
CREATE INDEX IF NOT EXISTS idx_incidents_severity ON incidents(severity);
|
|
726
|
+
CREATE INDEX IF NOT EXISTS idx_incidents_status ON incidents(status);
|
|
727
|
+
CREATE INDEX IF NOT EXISTS idx_incidents_created ON incidents(created_at DESC);
|
|
728
|
+
CREATE INDEX IF NOT EXISTS idx_incidents_deployment ON incidents(triggered_by_deployment_id);
|
|
729
|
+
|
|
730
|
+
-- rollback_executions: record of a rollback action, optionally tied to an incident
|
|
731
|
+
CREATE TABLE IF NOT EXISTS rollback_executions (
|
|
732
|
+
id TEXT PRIMARY KEY,
|
|
733
|
+
deployment_id TEXT NOT NULL REFERENCES deployments(id) ON DELETE RESTRICT,
|
|
734
|
+
incident_id TEXT REFERENCES incidents(id) ON DELETE SET NULL,
|
|
735
|
+
new_deployment_id TEXT REFERENCES deployments(id) ON DELETE SET NULL,
|
|
736
|
+
status TEXT NOT NULL DEFAULT 'planned' CHECK(status IN ('planned','approved','dry_run','executing','success','failed','cancelled')),
|
|
737
|
+
plan TEXT NOT NULL,
|
|
738
|
+
executed_at INTEGER,
|
|
739
|
+
created_at INTEGER NOT NULL,
|
|
740
|
+
metadata JSON,
|
|
741
|
+
CHECK(deployment_id <> ''),
|
|
742
|
+
CHECK(plan <> '')
|
|
743
|
+
);
|
|
744
|
+
CREATE INDEX IF NOT EXISTS idx_rollbacks_deployment ON rollback_executions(deployment_id);
|
|
745
|
+
CREATE INDEX IF NOT EXISTS idx_rollbacks_incident ON rollback_executions(incident_id);
|
|
746
|
+
CREATE INDEX IF NOT EXISTS idx_rollbacks_new_deployment ON rollback_executions(new_deployment_id);
|
|
747
|
+
CREATE INDEX IF NOT EXISTS idx_rollbacks_status ON rollback_executions(status);
|
|
748
|
+
CREATE INDEX IF NOT EXISTS idx_rollbacks_created ON rollback_executions(created_at DESC);
|
|
749
|
+
`;
|
|
750
|
+
|
|
751
|
+
// ── v14: analyses table ─────────────────────────────────────────────────────
|
|
752
|
+
|
|
753
|
+
export const SCHEMA_V14_SQL = `
|
|
754
|
+
-- v14: standalone analyses table (linkable to plans)
|
|
755
|
+
CREATE TABLE IF NOT EXISTS analyses (
|
|
756
|
+
id TEXT PRIMARY KEY,
|
|
757
|
+
slug TEXT NOT NULL,
|
|
758
|
+
title TEXT NOT NULL,
|
|
759
|
+
project_path TEXT NOT NULL,
|
|
760
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
761
|
+
findings_json TEXT NOT NULL DEFAULT '[]',
|
|
762
|
+
source_plan_id TEXT,
|
|
763
|
+
agent TEXT NOT NULL DEFAULT 'ranger',
|
|
764
|
+
session_id TEXT,
|
|
765
|
+
created_by TEXT,
|
|
766
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
767
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
768
|
+
archived_at TEXT,
|
|
769
|
+
FOREIGN KEY (source_plan_id) REFERENCES plans(id) ON DELETE SET NULL,
|
|
770
|
+
UNIQUE (slug, project_path)
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
CREATE INDEX IF NOT EXISTS idx_analyses_slug ON analyses(slug);
|
|
774
|
+
CREATE INDEX IF NOT EXISTS idx_analyses_project ON analyses(project_path);
|
|
775
|
+
CREATE INDEX IF NOT EXISTS idx_analyses_archived ON analyses(archived_at);
|
|
776
|
+
CREATE INDEX IF NOT EXISTS idx_analyses_source_plan ON analyses(source_plan_id);
|
|
777
|
+
CREATE INDEX IF NOT EXISTS idx_analyses_agent ON analyses(agent);
|
|
778
|
+
|
|
779
|
+
-- FTS5 virtual table for full-text search
|
|
780
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS analyses_fts USING fts5(
|
|
781
|
+
title,
|
|
782
|
+
summary,
|
|
783
|
+
findings_json,
|
|
784
|
+
content='analyses',
|
|
785
|
+
content_rowid='rowid',
|
|
786
|
+
tokenize='unicode61 remove_diacritics 1'
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
-- Sync triggers: keep analyses_fts in sync with analyses
|
|
790
|
+
CREATE TRIGGER IF NOT EXISTS analyses_ai AFTER INSERT ON analyses BEGIN
|
|
791
|
+
INSERT INTO analyses_fts(rowid, title, summary, findings_json)
|
|
792
|
+
VALUES (new.rowid, new.title, new.summary, new.findings_json);
|
|
793
|
+
END;
|
|
794
|
+
|
|
795
|
+
CREATE TRIGGER IF NOT EXISTS analyses_ad AFTER DELETE ON analyses BEGIN
|
|
796
|
+
INSERT INTO analyses_fts(analyses_fts, rowid, title, summary, findings_json)
|
|
797
|
+
VALUES ('delete', old.rowid, old.title, old.summary, old.findings_json);
|
|
798
|
+
END;
|
|
799
|
+
|
|
800
|
+
CREATE TRIGGER IF NOT EXISTS analyses_au AFTER UPDATE ON analyses BEGIN
|
|
801
|
+
INSERT INTO analyses_fts(analyses_fts, rowid, title, summary, findings_json)
|
|
802
|
+
VALUES ('delete', old.rowid, old.title, old.summary, old.findings_json);
|
|
803
|
+
INSERT INTO analyses_fts(rowid, title, summary, findings_json)
|
|
804
|
+
VALUES (new.rowid, new.title, new.summary, new.findings_json);
|
|
805
|
+
END;
|
|
806
|
+
`;
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* v15: rename analysis finding keys — data-only backfill.
|
|
810
|
+
*
|
|
811
|
+
* Pure data migration: no DDL. The actual rename of description→observation
|
|
812
|
+
* and recommendation→proposedAction happens in runMigrations() (migrations.ts)
|
|
813
|
+
* via backfillAnalysisFindings(). This SQL string is intentionally empty
|
|
814
|
+
* because:
|
|
815
|
+
* - SQLite lacks JSON key rename via DDL
|
|
816
|
+
* - The backfill needs TypeScript control flow for idempotency checks
|
|
817
|
+
*
|
|
818
|
+
* See: backfillAnalysisFindings() in src/db/migrations.ts
|
|
819
|
+
*/
|
|
820
|
+
export const SCHEMA_V15_SQL =
|
|
821
|
+
"-- v15: data-only backfill (description→observation, recommendation→proposedAction) executed in runMigrations()";
|
|
822
|
+
|
|
823
|
+
export const MIGRATIONS: Array<{
|
|
824
|
+
version: number;
|
|
825
|
+
description: string;
|
|
826
|
+
sql: string;
|
|
827
|
+
}> = [
|
|
828
|
+
{
|
|
829
|
+
version: 1,
|
|
830
|
+
description: "initial schema: plans + plan_tasks + sessions + FTS5",
|
|
831
|
+
sql: SCHEMA_V1_SQL,
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
version: 2,
|
|
835
|
+
description: "discriminated metadata + audit columns + M:N tags + FTS5 v2",
|
|
836
|
+
sql: SCHEMA_V2_SQL,
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
version: 3,
|
|
840
|
+
description:
|
|
841
|
+
"audit fixes: updated_at triggers, metadata defaults, composite indexes, session FK validation",
|
|
842
|
+
sql: SCHEMA_V3_SQL,
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
version: 4,
|
|
846
|
+
description:
|
|
847
|
+
"v4 low-priority fixes: priority/slug validation, plan_progress view, FTS5 diacritics, metadata default trigger",
|
|
848
|
+
sql: SCHEMA_V4_SQL,
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
version: 5,
|
|
852
|
+
description: "soft delete: archived_at columns on plans/tasks/sessions + composite indexes",
|
|
853
|
+
sql: SCHEMA_V5_SQL,
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
version: 6,
|
|
857
|
+
description: "write-once audit trail: original_plan_data on plans + plan_tasks",
|
|
858
|
+
sql: SCHEMA_V6_SQL,
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
version: 7,
|
|
862
|
+
description: "plan_files join table: M:N plans-files with role",
|
|
863
|
+
sql: SCHEMA_V7_SQL,
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
version: 8,
|
|
867
|
+
description:
|
|
868
|
+
"agent execution tracking: created_by_agent, executed_by_agent, executed_by_session on plans",
|
|
869
|
+
sql: SCHEMA_V8_SQL,
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
version: 9,
|
|
873
|
+
description: "plan_progress view fix: exclude archived plans from progress view",
|
|
874
|
+
sql: SCHEMA_V9_SQL,
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
version: 10,
|
|
878
|
+
description: "plan_files multi-role PK: (plan_id, file_path, role) + created_at column",
|
|
879
|
+
sql: SCHEMA_V10_SQL,
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
version: 11,
|
|
883
|
+
description: "background_tasks table for DB-backed task dispatch persistence",
|
|
884
|
+
sql: SCHEMA_V11_SQL,
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
version: 12,
|
|
888
|
+
description: "DB optimization: plan_progress views, composite indexes, plan_audit skeleton",
|
|
889
|
+
sql: SCHEMA_V12_SQL,
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
version: 13,
|
|
893
|
+
description: "ops tables: environments, releases, deployments, incidents, rollback_executions",
|
|
894
|
+
sql: SCHEMA_V13_SQL,
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
version: 14,
|
|
898
|
+
description: "analyses table + FTS5 (standalone, linkable to plans)",
|
|
899
|
+
sql: SCHEMA_V14_SQL,
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
version: 15,
|
|
903
|
+
description:
|
|
904
|
+
"rename analysis finding keys: description→observation, recommendation→proposedAction (data-only backfill)",
|
|
905
|
+
sql: SCHEMA_V15_SQL,
|
|
906
|
+
},
|
|
907
|
+
];
|