whipped 0.0.1 → 0.1.1

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.
@@ -0,0 +1,101 @@
1
+ -- =====================================================================
2
+ -- 001 initial — core state (workspaces, workflows, configs)
3
+ --
4
+ -- Replaces:
5
+ -- ~/.whipped/config.json
6
+ -- ~/.whipped/projects-layout.json
7
+ -- ~/.whipped/workspaces/index.json
8
+ -- ~/.whipped/workspaces/<id>/project-config.json
9
+ --
10
+ -- Board state (board.json + meta.json) moves in 002.
11
+ -- Memory subsystem (banks, memories, pending) is a later migration.
12
+ -- =====================================================================
13
+
14
+ -- =====================================================================
15
+ -- WORKSPACES
16
+ -- =====================================================================
17
+ -- ID matches existing format: randomBytes(4).toString("hex") → 8 hex chars.
18
+ -- settings_json holds the rest of runtimeProjectConfigSchema (autonomousModeEnabled,
19
+ -- autoPR, autoCommit, defaultAgent, gitInstructions, systemPrompt, previewUrl, etc.).
20
+ CREATE TABLE workspaces (
21
+ id TEXT PRIMARY KEY,
22
+ repo_path TEXT NOT NULL UNIQUE,
23
+ name TEXT NOT NULL,
24
+ git_remote_url TEXT,
25
+ settings_json TEXT NOT NULL DEFAULT '{}',
26
+ archived_at INTEGER,
27
+ created_at INTEGER NOT NULL,
28
+ updated_at INTEGER NOT NULL
29
+ );
30
+
31
+ -- =====================================================================
32
+ -- WORKSPACE INTEGRATIONS (github, jira, slack)
33
+ -- =====================================================================
34
+ -- config_json shape per type matches existing zod schemas
35
+ -- (runtimeGithubConfigSchema, runtimeJiraConfigSchema, slack pieces).
36
+ -- Tokens currently live here inline; future migration may move them to
37
+ -- workspace_secrets with encryption.
38
+ CREATE TABLE workspace_integrations (
39
+ workspace_id TEXT NOT NULL,
40
+ type TEXT NOT NULL CHECK (type IN ('github', 'jira', 'slack')),
41
+ enabled INTEGER NOT NULL DEFAULT 0,
42
+ config_json TEXT NOT NULL DEFAULT '{}',
43
+ updated_at INTEGER NOT NULL,
44
+ PRIMARY KEY (workspace_id, type),
45
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
46
+ );
47
+
48
+ -- =====================================================================
49
+ -- WORKSPACE SECRETS (matches existing runtimeProjectSecretSchema)
50
+ -- =====================================================================
51
+ CREATE TABLE workspace_secrets (
52
+ workspace_id TEXT NOT NULL,
53
+ key TEXT NOT NULL,
54
+ value TEXT NOT NULL,
55
+ PRIMARY KEY (workspace_id, key),
56
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
57
+ );
58
+
59
+ -- =====================================================================
60
+ -- WORKFLOWS (per-workspace, multiple named; for_story distinguishes
61
+ -- task-mode workflows from story-mode workflows)
62
+ -- =====================================================================
63
+ CREATE TABLE workflows (
64
+ id TEXT PRIMARY KEY,
65
+ workspace_id TEXT NOT NULL,
66
+ name TEXT NOT NULL,
67
+ is_default INTEGER NOT NULL DEFAULT 0,
68
+ for_story INTEGER NOT NULL DEFAULT 0,
69
+ slots_json TEXT NOT NULL,
70
+ created_at INTEGER NOT NULL,
71
+ updated_at INTEGER NOT NULL,
72
+ UNIQUE (workspace_id, name),
73
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
74
+ );
75
+
76
+ CREATE INDEX idx_workflows_workspace_default
77
+ ON workflows(workspace_id, is_default, for_story);
78
+
79
+ -- =====================================================================
80
+ -- GLOBAL CONFIG (singleton; parsed via runtimeGlobalConfigSchema)
81
+ -- =====================================================================
82
+ CREATE TABLE global_config (
83
+ id INTEGER PRIMARY KEY CHECK (id = 1),
84
+ config_json TEXT NOT NULL DEFAULT '{}',
85
+ updated_at INTEGER NOT NULL
86
+ );
87
+
88
+ INSERT INTO global_config (id, config_json, updated_at)
89
+ VALUES (1, '{}', strftime('%s', 'now') * 1000);
90
+
91
+ -- =====================================================================
92
+ -- PROJECTS LAYOUT (singleton; sidebar folder/order; parsed via projectsLayoutSchema)
93
+ -- =====================================================================
94
+ CREATE TABLE projects_layout (
95
+ id INTEGER PRIMARY KEY CHECK (id = 1),
96
+ layout_json TEXT NOT NULL DEFAULT '{"version":1,"topLevel":[],"folders":{}}',
97
+ updated_at INTEGER NOT NULL
98
+ );
99
+
100
+ INSERT INTO projects_layout (id, layout_json, updated_at)
101
+ VALUES (1, '{"version":1,"topLevel":[],"folders":{}}', strftime('%s', 'now') * 1000);
@@ -0,0 +1,134 @@
1
+ -- =====================================================================
2
+ -- 002 board state — cards, dependencies, activity, reviews, sessions
3
+ --
4
+ -- Replaces:
5
+ -- ~/.whipped/workspaces/<id>/board.json
6
+ -- ~/.whipped/workspaces/<id>/meta.json
7
+ --
8
+ -- Terminal buffers (.ansi files) and attachments stay as files.
9
+ -- =====================================================================
10
+
11
+ -- =====================================================================
12
+ -- Add board_revision to workspaces (was meta.json's `revision` field for
13
+ -- optimistic concurrency on full-board saves).
14
+ -- =====================================================================
15
+ ALTER TABLE workspaces ADD COLUMN board_revision INTEGER NOT NULL DEFAULT 0;
16
+
17
+ -- =====================================================================
18
+ -- CARDS (matches runtimeBoardCardSchema)
19
+ -- =====================================================================
20
+ -- column_position is an integer; reorder by renumbering within (workspace_id, column_id).
21
+ -- Small N per column makes renumbering cheap; sparse-int / lexorank is overkill.
22
+ --
23
+ -- JSON columns are used for nested shapes we never query independently:
24
+ -- description_attachments_json, pr_json, github_comment_ids_json.
25
+ -- Activity log, review comments, terminal sessions are normalized below.
26
+ CREATE TABLE cards (
27
+ id TEXT PRIMARY KEY,
28
+ workspace_id TEXT NOT NULL,
29
+ description TEXT NOT NULL,
30
+ description_attachments_json TEXT NOT NULL DEFAULT '[]',
31
+ column_id TEXT NOT NULL CHECK (column_id IN (
32
+ 'todo','in_progress','reopened',
33
+ 'ready_for_review','blocked','done'
34
+ )),
35
+ column_position INTEGER NOT NULL,
36
+ type TEXT NOT NULL DEFAULT 'task' CHECK (type IN ('task','story','subtask')),
37
+ ready_for_dev INTEGER NOT NULL DEFAULT 0,
38
+ agent_id TEXT CHECK (agent_id IN ('claude','codex','opencode','cursor')),
39
+ priority TEXT CHECK (priority IN ('urgent','high','medium','low')),
40
+ auto_fix_attempts INTEGER NOT NULL DEFAULT 0,
41
+ base_ref TEXT NOT NULL,
42
+ workflow_id TEXT,
43
+ github_issue_url TEXT,
44
+ pr_json TEXT,
45
+ jira_key TEXT,
46
+ jira_url TEXT,
47
+ github_comment_ids_json TEXT NOT NULL DEFAULT '[]',
48
+ worktree_path TEXT,
49
+ branch_name TEXT,
50
+ shared_worktree_id TEXT,
51
+ slack_message_ts TEXT,
52
+ slack_channel_id TEXT,
53
+ created_at INTEGER NOT NULL,
54
+ updated_at INTEGER NOT NULL,
55
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE,
56
+ FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON DELETE SET NULL
57
+ );
58
+
59
+ CREATE INDEX idx_cards_workspace_column
60
+ ON cards(workspace_id, column_id, column_position);
61
+ CREATE INDEX idx_cards_workflow ON cards(workflow_id);
62
+
63
+ -- =====================================================================
64
+ -- CARD DEPENDENCIES (was cards.dependsOn array)
65
+ -- =====================================================================
66
+ CREATE TABLE card_dependencies (
67
+ card_id TEXT NOT NULL,
68
+ depends_on_id TEXT NOT NULL,
69
+ PRIMARY KEY (card_id, depends_on_id),
70
+ FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE,
71
+ FOREIGN KEY (depends_on_id) REFERENCES cards(id) ON DELETE CASCADE
72
+ );
73
+
74
+ CREATE INDEX idx_card_deps_dep ON card_dependencies(depends_on_id);
75
+
76
+ -- =====================================================================
77
+ -- ACTIVITY LOG (was cards.activityLog array, runtimeActivityEntrySchema)
78
+ -- =====================================================================
79
+ CREATE TABLE activity_log (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ card_id TEXT NOT NULL,
82
+ timestamp INTEGER NOT NULL,
83
+ message TEXT NOT NULL,
84
+ FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE
85
+ );
86
+
87
+ CREATE INDEX idx_activity_card_time ON activity_log(card_id, timestamp);
88
+
89
+ -- =====================================================================
90
+ -- REVIEW COMMENTS (was cards.reviewComments, runtimeReviewCommentSchema)
91
+ -- Existing schema has no comment id; uses createdAt for lookup
92
+ -- (see linkCommentToSession in workspace-state.ts). Composite PK
93
+ -- (card_id, created_at) preserves that.
94
+ -- issues / attachments / metadata are JSON because they're small arrays/objects
95
+ -- that aren't queried independently.
96
+ -- =====================================================================
97
+ CREATE TABLE review_comments (
98
+ card_id TEXT NOT NULL,
99
+ created_at INTEGER NOT NULL,
100
+ type TEXT NOT NULL,
101
+ actor_type TEXT NOT NULL CHECK (actor_type IN ('ai', 'human', 'external')),
102
+ actor_id TEXT NOT NULL,
103
+ actor_source TEXT,
104
+ status TEXT CHECK (status IN ('pass', 'fail', 'warning', 'skipped')),
105
+ stream_id TEXT,
106
+ summary TEXT NOT NULL,
107
+ issues_json TEXT NOT NULL DEFAULT '[]',
108
+ attachments_json TEXT NOT NULL DEFAULT '[]',
109
+ metadata_json TEXT NOT NULL DEFAULT '{}',
110
+ PRIMARY KEY (card_id, created_at),
111
+ FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE
112
+ );
113
+
114
+ CREATE INDEX idx_review_comments_stream
115
+ ON review_comments(stream_id)
116
+ WHERE stream_id IS NOT NULL;
117
+
118
+ -- =====================================================================
119
+ -- TERMINAL SESSIONS (was cards.terminalSessions, runtimeTerminalSessionEntrySchema)
120
+ -- Buffers themselves stay as .ansi files under workspaces/<id>/buffers/.
121
+ -- =====================================================================
122
+ CREATE TABLE terminal_sessions (
123
+ card_id TEXT NOT NULL,
124
+ stream_id TEXT NOT NULL,
125
+ type TEXT NOT NULL,
126
+ started_at INTEGER NOT NULL,
127
+ ended_at INTEGER,
128
+ agent_id TEXT CHECK (agent_id IN ('claude','codex','opencode','cursor')),
129
+ state TEXT CHECK (state IN ('running','stopped','completed','failed','killed')),
130
+ PRIMARY KEY (card_id, stream_id),
131
+ FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE
132
+ );
133
+
134
+ CREATE INDEX idx_terminal_sessions_card ON terminal_sessions(card_id);
@@ -0,0 +1,81 @@
1
+ -- =====================================================================
2
+ -- 003 fix workflow PK (and cards → workflows FK)
3
+ --
4
+ -- workflows.id was incorrectly defined as a global PRIMARY KEY in 001, but
5
+ -- workflow IDs like "wf_default" / "wf_story_default" are canonical per-workspace
6
+ -- identifiers and repeat across workspaces. Importing legacy project-config.json
7
+ -- files surfaces this — saving workspace B's "wf_default" after workspace A's
8
+ -- triggers a UNIQUE constraint failure.
9
+ --
10
+ -- Fix: change workflows PRIMARY KEY to (workspace_id, id).
11
+ -- Side effect: cards.workflow_id FK → workflows.id is no longer valid (workflows.id
12
+ -- is no longer a standalone unique column). Drop the FK; workflow integrity is
13
+ -- enforced by app code instead.
14
+ -- =====================================================================
15
+
16
+ PRAGMA defer_foreign_keys = ON;
17
+
18
+ -- ── workflows: rebuild with compound PK ─────────────────────────────────────
19
+ CREATE TABLE workflows_new (
20
+ id TEXT NOT NULL,
21
+ workspace_id TEXT NOT NULL,
22
+ name TEXT NOT NULL,
23
+ is_default INTEGER NOT NULL DEFAULT 0,
24
+ for_story INTEGER NOT NULL DEFAULT 0,
25
+ slots_json TEXT NOT NULL,
26
+ created_at INTEGER NOT NULL,
27
+ updated_at INTEGER NOT NULL,
28
+ PRIMARY KEY (workspace_id, id),
29
+ UNIQUE (workspace_id, name),
30
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
31
+ );
32
+
33
+ INSERT INTO workflows_new (id, workspace_id, name, is_default, for_story, slots_json, created_at, updated_at)
34
+ SELECT id, workspace_id, name, is_default, for_story, slots_json, created_at, updated_at FROM workflows;
35
+
36
+ DROP INDEX IF EXISTS idx_workflows_workspace_default;
37
+ DROP TABLE workflows;
38
+ ALTER TABLE workflows_new RENAME TO workflows;
39
+ CREATE INDEX idx_workflows_workspace_default ON workflows(workspace_id, is_default, for_story);
40
+
41
+ -- ── cards: rebuild without FK to workflows ──────────────────────────────────
42
+ CREATE TABLE cards_new (
43
+ id TEXT PRIMARY KEY,
44
+ workspace_id TEXT NOT NULL,
45
+ description TEXT NOT NULL,
46
+ description_attachments_json TEXT NOT NULL DEFAULT '[]',
47
+ column_id TEXT NOT NULL CHECK (column_id IN (
48
+ 'todo','in_progress','reopened',
49
+ 'ready_for_review','blocked','done'
50
+ )),
51
+ column_position INTEGER NOT NULL,
52
+ type TEXT NOT NULL DEFAULT 'task' CHECK (type IN ('task','story','subtask')),
53
+ ready_for_dev INTEGER NOT NULL DEFAULT 0,
54
+ agent_id TEXT CHECK (agent_id IN ('claude','codex','opencode','cursor')),
55
+ priority TEXT CHECK (priority IN ('urgent','high','medium','low')),
56
+ auto_fix_attempts INTEGER NOT NULL DEFAULT 0,
57
+ base_ref TEXT NOT NULL,
58
+ workflow_id TEXT,
59
+ github_issue_url TEXT,
60
+ pr_json TEXT,
61
+ jira_key TEXT,
62
+ jira_url TEXT,
63
+ github_comment_ids_json TEXT NOT NULL DEFAULT '[]',
64
+ worktree_path TEXT,
65
+ branch_name TEXT,
66
+ shared_worktree_id TEXT,
67
+ slack_message_ts TEXT,
68
+ slack_channel_id TEXT,
69
+ created_at INTEGER NOT NULL,
70
+ updated_at INTEGER NOT NULL,
71
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
72
+ );
73
+
74
+ INSERT INTO cards_new SELECT * FROM cards;
75
+
76
+ DROP INDEX IF EXISTS idx_cards_workspace_column;
77
+ DROP INDEX IF EXISTS idx_cards_workflow;
78
+ DROP TABLE cards;
79
+ ALTER TABLE cards_new RENAME TO cards;
80
+ CREATE INDEX idx_cards_workspace_column ON cards(workspace_id, column_id, column_position);
81
+ CREATE INDEX idx_cards_workflow ON cards(workflow_id);
@@ -0,0 +1,50 @@
1
+ -- =====================================================================
2
+ -- 004 review_comments PK fix
3
+ --
4
+ -- 002 defined review_comments with PRIMARY KEY (card_id, created_at), which
5
+ -- assumed createdAt is unique per card. In practice two PR comments can land
6
+ -- on the same millisecond (or the poller can merge in a duplicate), tripping
7
+ -- the UNIQUE constraint:
8
+ --
9
+ -- SqliteError: UNIQUE constraint failed: review_comments.card_id, review_comments.created_at
10
+ --
11
+ -- Replace the composite PK with a synthetic AUTOINCREMENT id. Keep
12
+ -- (card_id, created_at) as a non-unique index so the linkCommentToSession
13
+ -- lookup stays fast (it updates all rows matching the timestamp, which is
14
+ -- the correct behaviour when duplicates do exist).
15
+ -- =====================================================================
16
+
17
+ PRAGMA defer_foreign_keys = ON;
18
+
19
+ CREATE TABLE review_comments_new (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ card_id TEXT NOT NULL,
22
+ created_at INTEGER NOT NULL,
23
+ type TEXT NOT NULL,
24
+ actor_type TEXT NOT NULL CHECK (actor_type IN ('ai', 'human', 'external')),
25
+ actor_id TEXT NOT NULL,
26
+ actor_source TEXT,
27
+ status TEXT CHECK (status IN ('pass', 'fail', 'warning', 'skipped')),
28
+ stream_id TEXT,
29
+ summary TEXT NOT NULL,
30
+ issues_json TEXT NOT NULL DEFAULT '[]',
31
+ attachments_json TEXT NOT NULL DEFAULT '[]',
32
+ metadata_json TEXT NOT NULL DEFAULT '{}',
33
+ FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE
34
+ );
35
+
36
+ INSERT INTO review_comments_new
37
+ (card_id, created_at, type, actor_type, actor_id, actor_source,
38
+ status, stream_id, summary, issues_json, attachments_json, metadata_json)
39
+ SELECT card_id, created_at, type, actor_type, actor_id, actor_source,
40
+ status, stream_id, summary, issues_json, attachments_json, metadata_json
41
+ FROM review_comments;
42
+
43
+ DROP INDEX IF EXISTS idx_review_comments_stream;
44
+ DROP TABLE review_comments;
45
+ ALTER TABLE review_comments_new RENAME TO review_comments;
46
+
47
+ CREATE INDEX idx_review_comments_card_time ON review_comments(card_id, created_at);
48
+ CREATE INDEX idx_review_comments_stream
49
+ ON review_comments(stream_id)
50
+ WHERE stream_id IS NOT NULL;
@@ -0,0 +1,28 @@
1
+ -- =====================================================================
2
+ -- 005 forbid self-referential card dependencies
3
+ --
4
+ -- card_dependencies allowed (card_id = depends_on_id) — a card depending
5
+ -- on itself. The DB wouldn't fail but the semantics are broken. Add a
6
+ -- CHECK constraint by recreating the table.
7
+ -- =====================================================================
8
+
9
+ PRAGMA defer_foreign_keys = ON;
10
+
11
+ CREATE TABLE card_dependencies_new (
12
+ card_id TEXT NOT NULL,
13
+ depends_on_id TEXT NOT NULL,
14
+ PRIMARY KEY (card_id, depends_on_id),
15
+ CHECK (card_id != depends_on_id),
16
+ FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE,
17
+ FOREIGN KEY (depends_on_id) REFERENCES cards(id) ON DELETE CASCADE
18
+ );
19
+
20
+ -- Skip any pre-existing self-references when copying (shouldn't happen, but
21
+ -- be defensive in case any slipped in before the check existed).
22
+ INSERT INTO card_dependencies_new (card_id, depends_on_id)
23
+ SELECT card_id, depends_on_id FROM card_dependencies WHERE card_id != depends_on_id;
24
+
25
+ DROP INDEX IF EXISTS idx_card_deps_dep;
26
+ DROP TABLE card_dependencies;
27
+ ALTER TABLE card_dependencies_new RENAME TO card_dependencies;
28
+ CREATE INDEX idx_card_deps_dep ON card_dependencies(depends_on_id);
@@ -0,0 +1,76 @@
1
+ -- =====================================================================
2
+ -- 006 memory — scoped agent memory + project state
3
+ --
4
+ -- Two scopes: 'global' (cross-project, workspace_id NULL) and 'project'
5
+ -- (one workspace). SQLite is the source of truth. Pending proposals live
6
+ -- in the same table via the status column; rows whose origin card is
7
+ -- deleted cascade away.
8
+ -- =====================================================================
9
+
10
+ CREATE TABLE memories (
11
+ id TEXT PRIMARY KEY,
12
+ scope TEXT NOT NULL CHECK (scope IN ('global', 'project')),
13
+ workspace_id TEXT,
14
+ type TEXT NOT NULL CHECK (type IN (
15
+ 'fact', 'convention', 'decision', 'preference', 'rule', 'lesson', 'sharp_edge'
16
+ )),
17
+ title TEXT NOT NULL,
18
+ content TEXT NOT NULL,
19
+ source_type TEXT NOT NULL CHECK (source_type IN (
20
+ 'user_correction', 'explicit_save', 'task_lesson', 'manual_human'
21
+ )),
22
+ importance INTEGER NOT NULL DEFAULT 1, -- 1-3, drives injection priority when filtering
23
+ always_inject INTEGER NOT NULL DEFAULT 0, -- pin into every prompt
24
+ origin_card_id TEXT, -- provenance (nullable)
25
+ origin_agent TEXT, -- JSON {agent, model}
26
+ status TEXT NOT NULL DEFAULT 'approved' CHECK (status IN ('pending', 'approved')),
27
+ created_at INTEGER NOT NULL,
28
+ updated_at INTEGER NOT NULL,
29
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE,
30
+ -- SET NULL (not CASCADE): deleting a card keeps approved memories it produced,
31
+ -- just dropping the provenance link. Pending proposals are cleaned up
32
+ -- explicitly via deletePendingMemoriesForCard() in the card lifecycle hooks.
33
+ FOREIGN KEY (origin_card_id) REFERENCES cards(id) ON DELETE SET NULL,
34
+ CHECK (
35
+ (scope = 'project' AND workspace_id IS NOT NULL) OR
36
+ (scope = 'global' AND workspace_id IS NULL)
37
+ )
38
+ );
39
+
40
+ CREATE INDEX idx_memories_scope ON memories(scope, workspace_id, status);
41
+ CREATE INDEX idx_memories_origin ON memories(origin_card_id);
42
+
43
+ -- FTS5 over title + content (external-content table mirrors `memories`).
44
+ CREATE VIRTUAL TABLE memories_fts USING fts5(
45
+ title, content,
46
+ content='memories',
47
+ content_rowid='rowid',
48
+ tokenize='porter unicode61'
49
+ );
50
+
51
+ CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN
52
+ INSERT INTO memories_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content);
53
+ END;
54
+
55
+ CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN
56
+ INSERT INTO memories_fts(memories_fts, rowid, title, content)
57
+ VALUES ('delete', old.rowid, old.title, old.content);
58
+ END;
59
+
60
+ CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN
61
+ INSERT INTO memories_fts(memories_fts, rowid, title, content)
62
+ VALUES ('delete', old.rowid, old.title, old.content);
63
+ INSERT INTO memories_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content);
64
+ END;
65
+
66
+ -- =====================================================================
67
+ -- PROJECT STATE — one row per workspace, injected wholesale into prompts.
68
+ -- =====================================================================
69
+ CREATE TABLE project_state (
70
+ workspace_id TEXT PRIMARY KEY,
71
+ tech_stack TEXT, -- free text / JSON
72
+ constraints TEXT, -- e.g. "no Redis in MVP"
73
+ goals TEXT,
74
+ updated_at INTEGER NOT NULL,
75
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
76
+ );
@@ -0,0 +1,10 @@
1
+ -- =====================================================================
2
+ -- 007 drop always_inject + project_state
3
+ --
4
+ -- always_inject only controlled prompt placement, not inclusion — confusing
5
+ -- and not worth keeping. project_state (tech_stack/constraints/goals) duplicated
6
+ -- the per-project system prompt, so it's removed in favour of one place.
7
+ -- =====================================================================
8
+
9
+ DROP TABLE IF EXISTS project_state;
10
+ ALTER TABLE memories DROP COLUMN always_inject;
@@ -0,0 +1,52 @@
1
+ -- =====================================================================
2
+ -- 007 memory tags — tag-routed global memory
3
+ --
4
+ -- Global memory is no longer injected into every project. It carries tags;
5
+ -- projects subscribe to tags; a global memory reaches a project when they
6
+ -- share a tag, when the project is the memory's origin, or when a human
7
+ -- explicitly binds the memory to the project by id. See docs/memory-tags.md.
8
+ -- =====================================================================
9
+
10
+ -- Origin workspace for the safety net: a global memory is always visible in
11
+ -- the repo that produced it, regardless of tag match. SET NULL on delete so
12
+ -- the memory survives if its origin workspace is removed (it then routes by
13
+ -- tag/binding only).
14
+ ALTER TABLE memories ADD COLUMN origin_workspace_id TEXT
15
+ REFERENCES workspaces(id) ON DELETE SET NULL;
16
+
17
+ -- Canonical tag vocabulary — one spelling per concept. Both join tables FK
18
+ -- into this, so a tag must exist before it can be attached (reuse-first).
19
+ CREATE TABLE tags (
20
+ name TEXT PRIMARY KEY -- normalised lowercase kebab-case
21
+ );
22
+
23
+ -- memory ↔ tag
24
+ CREATE TABLE memory_tags (
25
+ memory_id TEXT NOT NULL,
26
+ tag TEXT NOT NULL,
27
+ PRIMARY KEY (memory_id, tag),
28
+ FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE,
29
+ FOREIGN KEY (tag) REFERENCES tags(name) ON DELETE CASCADE
30
+ );
31
+
32
+ -- project's subscribed tags (human-curated in project settings)
33
+ CREATE TABLE workspace_tags (
34
+ workspace_id TEXT NOT NULL,
35
+ tag TEXT NOT NULL,
36
+ PRIMARY KEY (workspace_id, tag),
37
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE,
38
+ FOREIGN KEY (tag) REFERENCES tags(name) ON DELETE CASCADE
39
+ );
40
+
41
+ -- explicit by-id binding of a global memory to a project (human-curated)
42
+ CREATE TABLE memory_workspace_bindings (
43
+ memory_id TEXT NOT NULL,
44
+ workspace_id TEXT NOT NULL,
45
+ PRIMARY KEY (memory_id, workspace_id),
46
+ FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE,
47
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
48
+ );
49
+
50
+ CREATE INDEX idx_memory_tags_tag ON memory_tags(tag);
51
+ CREATE INDEX idx_workspace_tags_ws ON workspace_tags(workspace_id);
52
+ CREATE INDEX idx_memories_origin_ws ON memories(origin_workspace_id);
@@ -0,0 +1,44 @@
1
+ -- =====================================================================
2
+ -- 009 card relations
3
+ --
4
+ -- Splits the overloaded card_dependencies many-to-many table into three
5
+ -- explicit relations:
6
+ -- cards.depends_on_id : single-parent stacking. The child continues in the
7
+ -- parent's worktree/branch and starts once the parent
8
+ -- reaches ready_for_review.
9
+ -- card_waits_for : many-parent gate (tasks only). The card starts only
10
+ -- once ALL listed cards are done (merged), in a fresh
11
+ -- worktree branched from baseRef.
12
+ -- card_subtasks : story -> its subtasks. The story triggers its
13
+ -- orchestrator workflow once every subtask is ready.
14
+ --
15
+ -- Worktree ownership is now resolved at runtime by walking these relations,
16
+ -- so the persisted shared_worktree_id column is removed.
17
+ -- =====================================================================
18
+
19
+ DROP TABLE IF EXISTS card_dependencies;
20
+
21
+ ALTER TABLE cards DROP COLUMN shared_worktree_id;
22
+ ALTER TABLE cards ADD COLUMN depends_on_id TEXT REFERENCES cards(id) ON DELETE SET NULL;
23
+
24
+ CREATE TABLE card_waits_for (
25
+ card_id TEXT NOT NULL,
26
+ waits_for_id TEXT NOT NULL,
27
+ PRIMARY KEY (card_id, waits_for_id),
28
+ CHECK (card_id != waits_for_id),
29
+ FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE,
30
+ FOREIGN KEY (waits_for_id) REFERENCES cards(id) ON DELETE CASCADE
31
+ );
32
+
33
+ CREATE INDEX idx_card_waits_for_dep ON card_waits_for(waits_for_id);
34
+
35
+ CREATE TABLE card_subtasks (
36
+ story_id TEXT NOT NULL,
37
+ subtask_id TEXT NOT NULL,
38
+ PRIMARY KEY (story_id, subtask_id),
39
+ CHECK (story_id != subtask_id),
40
+ FOREIGN KEY (story_id) REFERENCES cards(id) ON DELETE CASCADE,
41
+ FOREIGN KEY (subtask_id) REFERENCES cards(id) ON DELETE CASCADE
42
+ );
43
+
44
+ CREATE INDEX idx_card_subtasks_sub ON card_subtasks(subtask_id);
@@ -0,0 +1,17 @@
1
+ -- =====================================================================
2
+ -- 010 review_comments stable application id
3
+ --
4
+ -- The integer `id` PK from 004 is AUTOINCREMENT and gets regenerated on
5
+ -- every card save (updateCard wipes + re-inserts all of a card's comments),
6
+ -- so it can't be used as a stable handle by the API/UI. Add a separate
7
+ -- `comment_id` TEXT that is generated once at creation and carried on the
8
+ -- in-memory comment, so it survives the delete+reinsert save cycle and gives
9
+ -- callers a stable id to address (e.g. DELETE a single comment).
10
+ -- =====================================================================
11
+
12
+ ALTER TABLE review_comments ADD COLUMN comment_id TEXT;
13
+
14
+ -- Backfill existing rows with a random 16-hex-char id (matches generateTaskId).
15
+ UPDATE review_comments SET comment_id = lower(hex(randomblob(8))) WHERE comment_id IS NULL;
16
+
17
+ CREATE INDEX idx_review_comments_comment_id ON review_comments(comment_id);
@@ -0,0 +1,14 @@
1
+ -- =====================================================================
2
+ -- 011 card plan + per-ticket model config
3
+ --
4
+ -- plan — text written by the one-shot plan agent, injected into
5
+ -- the dev agent's prompt.
6
+ -- active_level — workflow-wide capability level; every slot resolves it to
7
+ -- its own model pair (see resolvePair in api-contract).
8
+ -- model_config_json — snapshot of each slot's model pairs, copied from the
9
+ -- workflow at card creation and editable per ticket.
10
+ -- =====================================================================
11
+
12
+ ALTER TABLE cards ADD COLUMN plan TEXT;
13
+ ALTER TABLE cards ADD COLUMN active_level TEXT NOT NULL DEFAULT 'medium';
14
+ ALTER TABLE cards ADD COLUMN model_config_json TEXT;
@@ -0,0 +1,54 @@
1
+ -- =====================================================================
2
+ -- 012 recurring agents
3
+ --
4
+ -- Scheduled, one-shot agents created by the assistant (they can never create
5
+ -- themselves). They observe the board and report (no code-write tools) and keep
6
+ -- a private `journal` text column that carries state across runs — read at the
7
+ -- start of a run, rewritten at the end. Distinct from the project memory system.
8
+ --
9
+ -- schedule_kind — 'interval' (run every N seconds) or 'calendar' (cron_expr
10
+ -- in timezone, e.g. "0 9 * * 1" = every Monday 09:00).
11
+ -- agent_id/model/effort — a single fixed model pick (no level resolution, unlike
12
+ -- workflow slots — see AgentModelChoice in api-contract).
13
+ -- next_run_at — epoch ms of the next due run; the daemon's recurring-agent
14
+ -- scheduler polls for enabled agents whose next_run_at <= now.
15
+ -- =====================================================================
16
+
17
+ CREATE TABLE recurring_agents (
18
+ id TEXT PRIMARY KEY,
19
+ workspace_id TEXT NOT NULL,
20
+ name TEXT NOT NULL,
21
+ instructions TEXT NOT NULL DEFAULT '',
22
+ schedule_kind TEXT NOT NULL DEFAULT 'interval',
23
+ interval_seconds INTEGER,
24
+ cron_expr TEXT,
25
+ timezone TEXT,
26
+ agent_id TEXT NOT NULL DEFAULT 'claude',
27
+ model TEXT,
28
+ effort TEXT,
29
+ enabled INTEGER NOT NULL DEFAULT 1,
30
+ last_run_at INTEGER,
31
+ next_run_at INTEGER,
32
+ journal TEXT NOT NULL DEFAULT '',
33
+ created_at INTEGER NOT NULL,
34
+ updated_at INTEGER NOT NULL
35
+ );
36
+
37
+ CREATE INDEX idx_recurring_agents_workspace ON recurring_agents(workspace_id);
38
+ CREATE INDEX idx_recurring_agents_due ON recurring_agents(enabled, next_run_at);
39
+
40
+ CREATE TABLE recurring_agent_runs (
41
+ id TEXT PRIMARY KEY,
42
+ recurring_agent_id TEXT NOT NULL,
43
+ workspace_id TEXT NOT NULL,
44
+ stream_id TEXT,
45
+ started_at INTEGER NOT NULL,
46
+ ended_at INTEGER,
47
+ status TEXT NOT NULL DEFAULT 'running',
48
+ summary TEXT,
49
+ tokens INTEGER,
50
+ trigger TEXT NOT NULL DEFAULT 'schedule',
51
+ FOREIGN KEY (recurring_agent_id) REFERENCES recurring_agents(id) ON DELETE CASCADE
52
+ );
53
+
54
+ CREATE INDEX idx_recurring_agent_runs_agent ON recurring_agent_runs(recurring_agent_id, started_at);