@lmctl-ai/lmctl 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.
Files changed (29) hide show
  1. package/LICENSE +36 -0
  2. package/README.md +36 -0
  3. package/bin/lmctl +4 -0
  4. package/dist/cli/index.js +2180 -0
  5. package/dist/cli/schema.sql +660 -0
  6. package/dist/cli/side_effect_classifier.json +19 -0
  7. package/dist/config/ai_test_templates/example-test.md.template +50 -0
  8. package/dist/config/ai_test_templates/index.md.template +27 -0
  9. package/dist/config/durable_memory_templates/index.md.template +29 -0
  10. package/dist/config/durable_memory_templates/skills_general.md.template +42 -0
  11. package/dist/config/durable_memory_templates/skills_lmdebug.md.template +31 -0
  12. package/dist/config/durable_memory_templates/skills_lmprobe.md.template +31 -0
  13. package/package.json +42 -0
  14. package/workflows/bugfix-extended-v2.compound.json +1018 -0
  15. package/workflows/bugfix-v2.compound.json +856 -0
  16. package/workflows/claim-check-spike-v2.compound.json +136 -0
  17. package/workflows/document-creation.compound.json +200 -0
  18. package/workflows/durable-memory-consolidation-v2.compound.json +185 -0
  19. package/workflows/example-v2.compound.json +200 -0
  20. package/workflows/image-qa.compound.json +128 -0
  21. package/workflows/index.jsonl +15 -0
  22. package/workflows/info-qa.compound.json +120 -0
  23. package/workflows/newspaper.compound.json +129 -0
  24. package/workflows/pr-fix.compound.json +183 -0
  25. package/workflows/pr-followup-v2.compound.json +969 -0
  26. package/workflows/provider-probe.compound.json +107 -0
  27. package/workflows/qa-suite.compound.json +186 -0
  28. package/workflows/spec-driven-task.compound.json +460 -0
  29. package/workflows/triage-v2.compound.json +721 -0
@@ -0,0 +1,660 @@
1
+ -- lmctl-next v1 schema. Forward-only migration; see migrate.ts.
2
+ -- Timestamps are ISO-8601 UTC TEXT values.
3
+
4
+ -- Workflows (declarative, JSON-stored). Versioned.
5
+ CREATE TABLE workflow (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ name TEXT NOT NULL,
8
+ version INTEGER NOT NULL DEFAULT 1,
9
+ definition_schema_version INTEGER NOT NULL DEFAULT 1,
10
+ definition TEXT NOT NULL,
11
+ active_version_id INTEGER REFERENCES workflow_version(id),
12
+ created_at TEXT NOT NULL,
13
+ -- P2 (distribution-redesign §2): additive, nullable template provenance.
14
+ -- Populated when a workflow is loaded from a known template/path; NULL
15
+ -- everywhere else (zero back-compat break — old rows stay NULL). Mirrors
16
+ -- the v26 migration block byte-for-byte so a fresh schema.sql DB and a
17
+ -- v25→v26 migrated DB converge.
18
+ source_uri TEXT,
19
+ source_sha256 TEXT,
20
+ UNIQUE(name, version)
21
+ );
22
+
23
+ -- V3.1: immutable workflow JSON snapshots. Future editor flows append rows
24
+ -- here and point workflow.active_version_id at the current snapshot.
25
+ CREATE TABLE workflow_version (
26
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27
+ workflow_id INTEGER NOT NULL REFERENCES workflow(id),
28
+ version INTEGER NOT NULL,
29
+ definition TEXT NOT NULL,
30
+ author TEXT,
31
+ created_at TEXT NOT NULL,
32
+ UNIQUE(workflow_id, version)
33
+ );
34
+ CREATE INDEX IF NOT EXISTS idx_workflow_version_wf ON workflow_version(workflow_id, version DESC);
35
+
36
+ -- Teams (replaces .lmctl files).
37
+ CREATE TABLE team (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ name TEXT NOT NULL UNIQUE,
40
+ created_at TEXT NOT NULL
41
+ );
42
+
43
+ CREATE TABLE team_member (
44
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
45
+ team_id INTEGER NOT NULL REFERENCES team(id),
46
+ alias TEXT NOT NULL,
47
+ role TEXT NOT NULL DEFAULT 'agent',
48
+ provider TEXT NOT NULL,
49
+ model TEXT,
50
+ sessionid TEXT,
51
+ sessiondir TEXT NOT NULL,
52
+ state TEXT NOT NULL DEFAULT 'idle' CHECK(state IN ('idle','in_chat','attention_needed','retired')),
53
+ state_reason TEXT,
54
+ state_updated_at TEXT,
55
+ role_prompt TEXT,
56
+ prompt_snapshot TEXT,
57
+ provider_resolved TEXT,
58
+ created_at TEXT NOT NULL,
59
+ UNIQUE(team_id, alias)
60
+ );
61
+
62
+ -- Interpreter members can observe companion agent members.
63
+ CREATE TABLE team_member_companion (
64
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
65
+ interpreter_id INTEGER NOT NULL REFERENCES team_member(id),
66
+ companion_id INTEGER NOT NULL REFERENCES team_member(id),
67
+ created_at TEXT NOT NULL,
68
+ UNIQUE(interpreter_id, companion_id)
69
+ );
70
+
71
+ -- Projects (target repos/codebases). Workflow name resolves to a version at job->run creation.
72
+ -- v23: the per-project workflow lock columns (active_run_id / active_locked_at /
73
+ -- active_locked_by / active_locked_expires_at) were removed under the V4
74
+ -- master-agent architecture. Serialization is structural now.
75
+ CREATE TABLE project (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ name TEXT NOT NULL,
78
+ description TEXT,
79
+ local_path TEXT NOT NULL,
80
+ workflow_name TEXT NOT NULL,
81
+ team_id INTEGER NOT NULL REFERENCES team(id),
82
+ default_branch TEXT,
83
+ intake_config TEXT,
84
+ test_argv TEXT,
85
+ -- DG-6 D-6 / v31: per-project test command consumed by the
86
+ -- `lmctl/shell-step` `focused_test` step in the bugfix-* workflows.
87
+ -- When NULL or empty the `{{project.test_command}}` template
88
+ -- substituter falls back to `npm test` for backward-compat (matches
89
+ -- the historical hardcode the column replaces). Set to a project-
90
+ -- specific command (e.g. "go test ./...", "cargo test --quiet") to
91
+ -- target non-Node projects without forking the workflow JSON.
92
+ -- ADDITIVE ONLY — no existing column altered/dropped.
93
+ test_command TEXT,
94
+ durable_memory_mode TEXT NOT NULL DEFAULT 'folder' CHECK(durable_memory_mode IN ('folder','db')),
95
+ created_at TEXT NOT NULL,
96
+ UNIQUE(name)
97
+ );
98
+
99
+ -- App 2 / v17: project-to-workflow dispatcher rows. Each enabled row
100
+ -- can match an intake event and enqueue a job for its workflow.
101
+ CREATE TABLE project_workflow (
102
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
103
+ project_id INTEGER NOT NULL REFERENCES project(id),
104
+ workflow_id INTEGER NOT NULL REFERENCES workflow(id),
105
+ condition TEXT,
106
+ parameters_override TEXT,
107
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0,1)),
108
+ priority INTEGER NOT NULL DEFAULT 100,
109
+ created_at TEXT NOT NULL,
110
+ UNIQUE(project_id, workflow_id, condition)
111
+ );
112
+
113
+ -- Post-V3 Step 1 / v18: per-project issue queue. QA producers create
114
+ -- failures; PR-fix consumers atomically claim and close them.
115
+ CREATE TABLE project_issue (
116
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
117
+ project_id INTEGER NOT NULL REFERENCES project(id),
118
+ title TEXT NOT NULL,
119
+ body TEXT NOT NULL,
120
+ status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','assigned','closed','reopened')),
121
+ severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low','medium','high','critical')),
122
+ labels TEXT,
123
+ ai_test_path TEXT,
124
+ source_run_id INTEGER REFERENCES run(id),
125
+ assigned_run_id INTEGER REFERENCES run(id),
126
+ closed_run_id INTEGER REFERENCES run(id),
127
+ commit_hash TEXT,
128
+ created_at TEXT NOT NULL,
129
+ updated_at TEXT NOT NULL,
130
+ closed_at TEXT
131
+ );
132
+
133
+ -- Jobs: SQLite-backed queue.
134
+ CREATE TABLE job (
135
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
136
+ project_id INTEGER NOT NULL REFERENCES project(id),
137
+ workflow_id_override INTEGER REFERENCES workflow(id),
138
+ source TEXT NOT NULL,
139
+ source_ref TEXT,
140
+ payload TEXT,
141
+ status TEXT NOT NULL DEFAULT 'queued',
142
+ priority INTEGER NOT NULL DEFAULT 0,
143
+ available_at TEXT NOT NULL,
144
+ locked_by TEXT,
145
+ locked_until TEXT,
146
+ attempt_count INTEGER NOT NULL DEFAULT 0,
147
+ last_error TEXT,
148
+ dedupe_key TEXT,
149
+ concurrency_key_resolved TEXT,
150
+ enqueued_at TEXT NOT NULL,
151
+ started_at TEXT,
152
+ ended_at TEXT,
153
+ run_id INTEGER REFERENCES run(id),
154
+ UNIQUE(source, dedupe_key)
155
+ );
156
+
157
+ -- Runs: an instance of a workflow execution against a job.
158
+ CREATE TABLE run (
159
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
160
+ job_id INTEGER NOT NULL REFERENCES job(id),
161
+ workflow_id INTEGER NOT NULL REFERENCES workflow(id),
162
+ team_id INTEGER NOT NULL REFERENCES team(id),
163
+ current_state TEXT,
164
+ terminal_state TEXT,
165
+ workflow_version_id INTEGER REFERENCES workflow_version(id),
166
+ started_at TEXT NOT NULL,
167
+ ended_at TEXT,
168
+ paused_at TEXT,
169
+ blocker TEXT,
170
+ parent_run_id INTEGER REFERENCES run(id),
171
+ -- DEF-1 v27: step.id-keyed JSON map of the operator escalation-resume
172
+ -- decision. Additive nullable; old rows NULL. Mirrors the v27 migration.
173
+ pending_escalation_decision TEXT
174
+ );
175
+
176
+ -- Steps: one row per state transition inside a run.
177
+ CREATE TABLE step (
178
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
179
+ run_id INTEGER NOT NULL REFERENCES run(id),
180
+ parent_step_id INTEGER REFERENCES step(id),
181
+ state_name TEXT NOT NULL,
182
+ outcome TEXT,
183
+ status_detail TEXT,
184
+ started_at TEXT NOT NULL,
185
+ ended_at TEXT
186
+ );
187
+
188
+ -- V3.2: per-workflow trigger registrations polled by trigger_watcher.
189
+ CREATE TABLE trigger_instance (
190
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
191
+ workflow_id INTEGER NOT NULL REFERENCES workflow(id),
192
+ trigger_id TEXT NOT NULL,
193
+ trigger_type TEXT NOT NULL,
194
+ parameters TEXT NOT NULL,
195
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0,1)),
196
+ cursor TEXT,
197
+ last_poll_at TEXT,
198
+ last_failure_at TEXT,
199
+ last_failure TEXT,
200
+ created_at TEXT NOT NULL,
201
+ UNIQUE(workflow_id, trigger_id)
202
+ );
203
+
204
+ -- Step artifacts (transcripts, file paths, inline content).
205
+ --
206
+ -- `provider_call_mode` (A2.5): when the artifact was produced by a
207
+ -- provider-backed step (engine chat() call), this column records
208
+ -- whether the chat went through the in-process library path or the
209
+ -- legacy `lmctl chat` subprocess. Values: 'library' | 'subprocess'
210
+ -- | NULL (artifact wasn't produced by a chat call). Lets audits
211
+ -- answer "was this turn run in-process?" without grepping
212
+ -- process trees.
213
+ CREATE TABLE step_artifact (
214
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
215
+ step_id INTEGER NOT NULL REFERENCES step(id),
216
+ kind TEXT NOT NULL,
217
+ path TEXT,
218
+ content TEXT,
219
+ sha TEXT,
220
+ provider_call_mode TEXT,
221
+ created_at TEXT NOT NULL
222
+ );
223
+
224
+ -- Generic external object layer: PRs, issues, tickets, deployments, incidents, etc.
225
+ CREATE TABLE external_object (
226
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
227
+ type TEXT NOT NULL,
228
+ provider TEXT NOT NULL,
229
+ project_id INTEGER NOT NULL REFERENCES project(id),
230
+ run_id INTEGER REFERENCES run(id),
231
+ external_id TEXT NOT NULL,
232
+ url TEXT,
233
+ status TEXT,
234
+ metadata TEXT,
235
+ last_seen_at TEXT,
236
+ created_at TEXT NOT NULL,
237
+ UNIQUE(provider, type, external_id)
238
+ );
239
+
240
+ -- Idempotent signals observed on external objects.
241
+ CREATE TABLE external_signal (
242
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
243
+ object_id INTEGER NOT NULL REFERENCES external_object(id),
244
+ kind TEXT NOT NULL,
245
+ source TEXT,
246
+ occurred_at TEXT,
247
+ payload TEXT,
248
+ signal_hash TEXT,
249
+ handled_at TEXT,
250
+ created_at TEXT NOT NULL,
251
+ UNIQUE(object_id, kind, signal_hash)
252
+ );
253
+
254
+ -- Operator-facing alerts.
255
+ -- requires_user=1 when this attention demands explicit human action (e.g. resume
256
+ -- a Pause). UI flickers + multi-channel notification fires for these. Phase 3 L2.
257
+ CREATE TABLE attention (
258
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
259
+ kind TEXT NOT NULL,
260
+ severity TEXT,
261
+ project_id INTEGER REFERENCES project(id),
262
+ run_id INTEGER REFERENCES run(id),
263
+ external_object_id INTEGER REFERENCES external_object(id),
264
+ requires_user INTEGER NOT NULL DEFAULT 0,
265
+ payload TEXT,
266
+ created_at TEXT NOT NULL,
267
+ acknowledged_at TEXT
268
+ );
269
+
270
+ CREATE TABLE session_size (
271
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
272
+ team_name TEXT NOT NULL,
273
+ alias TEXT NOT NULL,
274
+ sessionid TEXT NOT NULL,
275
+ input_tokens INTEGER,
276
+ cache_tokens INTEGER,
277
+ output_tokens INTEGER,
278
+ total_tokens INTEGER,
279
+ observed_at TEXT NOT NULL
280
+ );
281
+
282
+ CREATE INDEX IF NOT EXISTS idx_job_status_avail ON job(status, available_at);
283
+ CREATE INDEX IF NOT EXISTS idx_job_dedupe ON job(source, dedupe_key);
284
+ CREATE INDEX IF NOT EXISTS idx_job_concurrency_running ON job(concurrency_key_resolved, status);
285
+ CREATE INDEX IF NOT EXISTS idx_project_workflow_project ON project_workflow(project_id, enabled);
286
+ CREATE INDEX IF NOT EXISTS idx_project_issue_status ON project_issue(project_id, status);
287
+ CREATE INDEX IF NOT EXISTS idx_project_issue_ai_test ON project_issue(project_id, ai_test_path) WHERE ai_test_path IS NOT NULL;
288
+ CREATE INDEX IF NOT EXISTS idx_step_run_started ON step(run_id, started_at);
289
+ CREATE INDEX IF NOT EXISTS idx_step_parent ON step(parent_step_id);
290
+ CREATE INDEX IF NOT EXISTS idx_trigger_instance_wf ON trigger_instance(workflow_id);
291
+ CREATE INDEX IF NOT EXISTS idx_trigger_instance_enabled ON trigger_instance(enabled) WHERE enabled = 1;
292
+ CREATE INDEX IF NOT EXISTS idx_extobj_run ON external_object(run_id, type);
293
+ CREATE INDEX IF NOT EXISTS idx_extobj_unhandled ON external_object(project_id, status, last_seen_at);
294
+ CREATE INDEX IF NOT EXISTS idx_extsig_unhandled ON external_signal(object_id, kind, handled_at);
295
+ CREATE INDEX IF NOT EXISTS idx_attention_unacked ON attention(kind, acknowledged_at);
296
+ CREATE INDEX IF NOT EXISTS idx_session_size_member ON session_size(team_name, alias, observed_at DESC);
297
+ CREATE INDEX IF NOT EXISTS idx_session_size_session ON session_size(sessionid, observed_at DESC);
298
+ CREATE INDEX IF NOT EXISTS idx_session_size_observed ON session_size(observed_at DESC);
299
+
300
+ -- Triage candidates (one row per evaluated issue produced by the triage workflow).
301
+ CREATE TABLE candidate_issue (
302
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
303
+ triage_run_id INTEGER NOT NULL REFERENCES run(id),
304
+ repo TEXT NOT NULL,
305
+ issue_url TEXT NOT NULL,
306
+ issue_number INTEGER NOT NULL,
307
+ title TEXT,
308
+ status TEXT NOT NULL,
309
+ reason TEXT,
310
+ confidence REAL,
311
+ local_path TEXT,
312
+ likely_files TEXT,
313
+ test_command TEXT,
314
+ lmdedup_summary TEXT,
315
+ created_at TEXT NOT NULL,
316
+ approved_at TEXT,
317
+ fix_job_id INTEGER REFERENCES job(id),
318
+ UNIQUE(triage_run_id, issue_url)
319
+ );
320
+ CREATE INDEX IF NOT EXISTS idx_candidate_issue_run ON candidate_issue(triage_run_id);
321
+ CREATE INDEX IF NOT EXISTS idx_candidate_issue_status ON candidate_issue(status);
322
+
323
+ -- Submitted PRs being watched by the PR follow-up workflow.
324
+ CREATE TABLE submitted_pr (
325
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
326
+ repo TEXT NOT NULL,
327
+ pr_url TEXT NOT NULL UNIQUE,
328
+ issue_url TEXT,
329
+ local_path TEXT,
330
+ branch TEXT,
331
+ head_sha TEXT,
332
+ status TEXT NOT NULL DEFAULT 'watching',
333
+ submitted_at TEXT NOT NULL,
334
+ last_scanned_at TEXT,
335
+ last_followup_at TEXT,
336
+ last_known_state TEXT,
337
+ last_known_checks TEXT,
338
+ last_known_review TEXT,
339
+ visibility_status TEXT,
340
+ created_by_run_id INTEGER REFERENCES run(id)
341
+ );
342
+ CREATE INDEX IF NOT EXISTS idx_submitted_pr_status ON submitted_pr(status);
343
+ CREATE INDEX IF NOT EXISTS idx_submitted_pr_scanned ON submitted_pr(last_scanned_at);
344
+
345
+ -- One row per delta observed on a submitted PR (comment, check, review, state change).
346
+ CREATE TABLE pr_followup_event (
347
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
348
+ submitted_pr_id INTEGER NOT NULL REFERENCES submitted_pr(id),
349
+ run_id INTEGER REFERENCES run(id),
350
+ event_kind TEXT NOT NULL,
351
+ source_url TEXT,
352
+ actor TEXT,
353
+ author_association TEXT,
354
+ body_hash TEXT,
355
+ classification TEXT,
356
+ action_required TEXT,
357
+ created_at TEXT NOT NULL,
358
+ seen_at TEXT,
359
+ resolved_at TEXT,
360
+ UNIQUE(submitted_pr_id, body_hash)
361
+ );
362
+ CREATE INDEX IF NOT EXISTS idx_pr_followup_event_pr ON pr_followup_event(submitted_pr_id);
363
+ CREATE INDEX IF NOT EXISTS idx_pr_followup_event_kind ON pr_followup_event(event_kind);
364
+
365
+ -- Durable memory entries (C.1). Used by DB-backend projects; folder-backend
366
+ -- projects store entries as .md files on disk under <project_local_path>/durable-memory/.
367
+ CREATE TABLE IF NOT EXISTS durable_memory (
368
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
369
+ project_id INTEGER NOT NULL REFERENCES project(id),
370
+ type TEXT NOT NULL CHECK (type IN ('user', 'feedback', 'project', 'reference')),
371
+ name TEXT NOT NULL,
372
+ description TEXT,
373
+ body TEXT NOT NULL,
374
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
375
+ updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
376
+ UNIQUE(project_id, name)
377
+ );
378
+ CREATE INDEX IF NOT EXISTS idx_durable_memory_project_type ON durable_memory(project_id, type);
379
+ CREATE INDEX IF NOT EXISTS idx_durable_memory_project_name ON durable_memory(project_id, name);
380
+
381
+ -- D.1: fine-grained agent activity events captured during chat turns.
382
+ CREATE TABLE IF NOT EXISTS session_event (
383
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
384
+ run_id INTEGER NOT NULL REFERENCES run(id),
385
+ step_id INTEGER REFERENCES step(id),
386
+ kind TEXT NOT NULL,
387
+ timestamp_ms INTEGER NOT NULL,
388
+ payload TEXT NOT NULL,
389
+ payload_truncated INTEGER NOT NULL DEFAULT 0,
390
+ overflow_artifact_id INTEGER REFERENCES step_artifact(id)
391
+ );
392
+ CREATE INDEX IF NOT EXISTS idx_session_event_run ON session_event(run_id);
393
+ CREATE INDEX IF NOT EXISTS idx_session_event_kind_step ON session_event(kind, step_id);
394
+ CREATE INDEX IF NOT EXISTS idx_session_event_timestamp ON session_event(timestamp_ms);
395
+
396
+ -- D.3: per-minute-bucket rolled-up stats; dashboard reads this, never session_event.
397
+ CREATE TABLE IF NOT EXISTS rollup_stats (
398
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
399
+ run_id INTEGER NOT NULL REFERENCES run(id),
400
+ step_id INTEGER REFERENCES step(id),
401
+ metric_kind TEXT NOT NULL,
402
+ value REAL NOT NULL,
403
+ bucket_start_ms INTEGER NOT NULL,
404
+ bucket_end_ms INTEGER NOT NULL,
405
+ updated_at INTEGER NOT NULL,
406
+ UNIQUE(run_id, step_id, metric_kind, bucket_start_ms)
407
+ );
408
+ CREATE INDEX IF NOT EXISTS idx_rollup_run_metric ON rollup_stats(run_id, metric_kind);
409
+ CREATE INDEX IF NOT EXISTS idx_rollup_bucket ON rollup_stats(bucket_start_ms);
410
+
411
+ -- D.4: detected drift signals flagged by the policy engine.
412
+ CREATE TABLE IF NOT EXISTS drift_signal (
413
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
414
+ team_member_id INTEGER NOT NULL REFERENCES team_member(id),
415
+ project_id INTEGER REFERENCES project(id),
416
+ signal_kind TEXT NOT NULL,
417
+ severity TEXT NOT NULL CHECK (severity IN ('low', 'medium', 'high')),
418
+ description TEXT NOT NULL,
419
+ detected_at INTEGER NOT NULL,
420
+ expires_at INTEGER,
421
+ acknowledged_at INTEGER,
422
+ payload TEXT
423
+ );
424
+ CREATE INDEX IF NOT EXISTS idx_drift_member ON drift_signal(team_member_id);
425
+ CREATE INDEX IF NOT EXISTS idx_drift_unacked ON drift_signal(team_member_id) WHERE acknowledged_at IS NULL;
426
+
427
+ -- D.3: monotonic cursor so the 250ms rollup updater survives daemon restarts.
428
+ CREATE TABLE IF NOT EXISTS rollup_cursor (
429
+ key TEXT PRIMARY KEY,
430
+ value INTEGER NOT NULL DEFAULT 0
431
+ );
432
+
433
+ -- R2.3: interactive_session table for multi-turn Interactive state.
434
+ CREATE TABLE IF NOT EXISTS interactive_session (
435
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
436
+ run_id INTEGER NOT NULL REFERENCES run(id),
437
+ step_id INTEGER NOT NULL REFERENCES step(id),
438
+ state_name TEXT NOT NULL,
439
+ turn_count INTEGER NOT NULL DEFAULT 0,
440
+ last_agent_output TEXT,
441
+ last_operator_input TEXT,
442
+ status TEXT NOT NULL CHECK (status IN ('waiting_operator', 'waiting_agent', 'done', 'timeout')),
443
+ started_at INTEGER NOT NULL,
444
+ last_activity_at INTEGER NOT NULL,
445
+ UNIQUE(step_id)
446
+ );
447
+ CREATE INDEX IF NOT EXISTS idx_interactive_status ON interactive_session(status);
448
+ CREATE INDEX IF NOT EXISTS idx_interactive_run_state ON interactive_session(run_id, state_name);
449
+
450
+ -- webui v2 / v19: per-user transition-graph session for the operator webui.
451
+ -- See briefs/webui-transition-graph-v2.md §"Schemas (v19 migration)".
452
+ CREATE TABLE IF NOT EXISTS webui_session (
453
+ session_id TEXT PRIMARY KEY,
454
+ user_id TEXT NOT NULL,
455
+ current_node TEXT NOT NULL,
456
+ node_params TEXT,
457
+ prev_node_stack TEXT,
458
+ pending_form TEXT,
459
+ chatbot_session_id TEXT,
460
+ auth_state TEXT NOT NULL,
461
+ last_transition_at TEXT NOT NULL,
462
+ created_at TEXT NOT NULL
463
+ );
464
+ CREATE INDEX IF NOT EXISTS webui_session_user_idx ON webui_session(user_id);
465
+
466
+ -- webui v2 / v19: chatbot sidekick conversations and messages.
467
+ CREATE TABLE IF NOT EXISTS chatbot_session (
468
+ chatbot_session_id TEXT PRIMARY KEY,
469
+ user_id TEXT NOT NULL,
470
+ title TEXT,
471
+ created_at TEXT NOT NULL,
472
+ last_active_at TEXT NOT NULL,
473
+ status TEXT NOT NULL DEFAULT 'active'
474
+ );
475
+ CREATE INDEX IF NOT EXISTS chatbot_session_user_idx ON chatbot_session(user_id, last_active_at DESC);
476
+
477
+ CREATE TABLE IF NOT EXISTS chatbot_message (
478
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
479
+ chatbot_session_id TEXT NOT NULL REFERENCES chatbot_session(chatbot_session_id) ON DELETE CASCADE,
480
+ role TEXT NOT NULL,
481
+ content TEXT NOT NULL,
482
+ tool_calls TEXT,
483
+ created_at TEXT NOT NULL
484
+ );
485
+ CREATE INDEX IF NOT EXISTS chatbot_message_session_idx ON chatbot_message(chatbot_session_id, id);
486
+
487
+ -- Phase A / v20: SQLite-canonical config table for api.url / api.port /
488
+ -- api.token. Eliminates env-var-propagation hazards when serve is launched
489
+ -- via run_in_background (Claude Code Bash strips env on spawn).
490
+ CREATE TABLE IF NOT EXISTS lmctl_config (
491
+ key TEXT PRIMARY KEY,
492
+ value TEXT NOT NULL,
493
+ updated_at TEXT NOT NULL
494
+ );
495
+
496
+ -- M1 / v25 (#218): composition-DSL source + shareable template
497
+ -- (template↔instance split) for the scripting frontend. ADDITIVE — no
498
+ -- existing table/column changed. The compiled IR still lives in
499
+ -- workflow.definition / workflow_version; compiled_workflow_version_id links
500
+ -- a unit to its compiled snapshot. Mirrors the migrate.ts v25 block so a
501
+ -- fresh DB and a v24→v25 migrated DB converge. See
502
+ -- durable-memory/composition-language-design.md §5.
503
+ CREATE TABLE IF NOT EXISTS composition_unit (
504
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
505
+ name TEXT NOT NULL,
506
+ version INTEGER NOT NULL,
507
+ lang TEXT NOT NULL,
508
+ source TEXT NOT NULL,
509
+ source_sha TEXT NOT NULL,
510
+ compiled_workflow_version_id INTEGER REFERENCES workflow_version(id),
511
+ created_at TEXT NOT NULL,
512
+ UNIQUE(name, version)
513
+ );
514
+ CREATE INDEX IF NOT EXISTS idx_composition_unit_name ON composition_unit(name, version DESC);
515
+
516
+ -- L-7 / v28: webui prompt → V4 job dispatch map. `submitPrompt(project_id,
517
+ -- text)` inserts a row + enqueues a `manual` job for the project's bound
518
+ -- workflow; `pollPrompt(id)` joins back to job/run to derive a {pending |
519
+ -- running | done | error} status + the run terminal_state. ADDITIVE — no
520
+ -- existing table/column changed. Mirrors the migrate.ts v28 block so a fresh
521
+ -- DB and a v27→v28 migrated DB converge. See
522
+ -- durable-memory/lmctl-next-migration-design-2026-05-24.md §C.3.
523
+ CREATE TABLE IF NOT EXISTS prompt_dispatch (
524
+ prompt_id TEXT PRIMARY KEY,
525
+ project_id INTEGER NOT NULL REFERENCES project(id),
526
+ job_id INTEGER NOT NULL REFERENCES job(id),
527
+ text TEXT NOT NULL,
528
+ context_json TEXT,
529
+ created_at TEXT NOT NULL,
530
+ completed_at TEXT
531
+ );
532
+ CREATE INDEX IF NOT EXISTS idx_prompt_dispatch_project ON prompt_dispatch(project_id, created_at DESC);
533
+ CREATE INDEX IF NOT EXISTS idx_prompt_dispatch_job ON prompt_dispatch(job_id);
534
+
535
+ -- L-8 Gap 4 / v29: webui workspace + project tables (K1 + K7 per
536
+ -- durable-memory/lmctl-next-migration-design-2026-05-24.md §C.2 / §K.7).
537
+ -- DELIBERATELY distinct from v27 `project` (which holds standing-team
538
+ -- projects, CLI-managed). `web_project` is what the webui's
539
+ -- createProject / updateProject / deleteProject mutate; soft-delete via
540
+ -- `deleted_at` so prompt history retains a reference but the UI hides the
541
+ -- row. ADDITIVE ONLY — no existing table/column altered or dropped. Mirrors
542
+ -- the migrate.ts v29 block so a fresh DB and a v28→v29 migrated DB converge.
543
+ CREATE TABLE IF NOT EXISTS workspace (
544
+ id TEXT PRIMARY KEY,
545
+ name TEXT NOT NULL,
546
+ idle_threshold_ms INTEGER NOT NULL DEFAULT 600000,
547
+ created_at TEXT NOT NULL,
548
+ deleted_at TEXT
549
+ );
550
+
551
+ CREATE TABLE IF NOT EXISTS web_project (
552
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
553
+ workspace_id TEXT NOT NULL REFERENCES workspace(id),
554
+ name TEXT NOT NULL,
555
+ repo_path TEXT NOT NULL,
556
+ config_json TEXT,
557
+ linked_v4_project_id INTEGER REFERENCES project(id),
558
+ last_active_at TEXT,
559
+ created_at TEXT NOT NULL,
560
+ deleted_at TEXT
561
+ );
562
+ CREATE INDEX IF NOT EXISTS idx_web_project_workspace ON web_project(workspace_id, deleted_at);
563
+ CREATE INDEX IF NOT EXISTS idx_web_project_v4_link ON web_project(linked_v4_project_id) WHERE linked_v4_project_id IS NOT NULL;
564
+
565
+ -- Seed the default workspace so `Query.workspace` always returns a row even
566
+ -- before the operator creates any project. The "default" id matches the
567
+ -- pre-L-8 resolver constant (preserves the `workspace.id == "default"`
568
+ -- baseline in tests/graphql/{batch,server}.test.ts).
569
+ INSERT OR IGNORE INTO workspace (id, name, idle_threshold_ms, created_at)
570
+ VALUES ('default', 'Default workspace', 600000, '1970-01-01T00:00:00.000Z');
571
+
572
+ -- MX-9 final fixup / v30 (durable-memory/MX-design-2026-05-25.md §2 lines 162,
573
+ -- 233): persist the agent-side mailbox cursor so restarts don't re-process the
574
+ -- entire backlog. Without this row, on agent restart the
575
+ -- VersionedMailboxClient starts with `cursor = undefined` → walks every
576
+ -- visible version as new → MailboxRpc.handleIncoming re-dispatches every
577
+ -- historical request envelope to the registered handler → duplicate
578
+ -- submitPrompt enqueues + duplicate job dispatch. ADDITIVE ONLY — no existing
579
+ -- table/column altered or dropped. Mirrors the migrate.ts v30 block so a fresh
580
+ -- DB and a v29→v30 migrated DB converge.
581
+ CREATE TABLE IF NOT EXISTS mailbox_cursor (
582
+ user_id TEXT NOT NULL,
583
+ mailbox_key TEXT NOT NULL,
584
+ last_processed_version_id TEXT,
585
+ last_processed_at TEXT NOT NULL,
586
+ -- v35: last column to match `ALTER TABLE ... ADD COLUMN` append order on
587
+ -- migrated DBs (see migrate.ts v35), so fresh and migrated converge.
588
+ last_processed_seq INTEGER,
589
+ PRIMARY KEY(user_id, mailbox_key)
590
+ );
591
+
592
+ -- Path-keyed root-team registry (NOT a reuse of team/team_member)
593
+ CREATE TABLE IF NOT EXISTS root_team (
594
+ teamfile_path TEXT PRIMARY KEY,
595
+ name TEXT,
596
+ raw_text TEXT NOT NULL,
597
+ raw_sha256 TEXT NOT NULL,
598
+ registered_at TEXT NOT NULL,
599
+ last_seeded_at TEXT
600
+ );
601
+
602
+ -- Cross-team connection snapshot (from _CONNECT_), captured at seed
603
+ CREATE TABLE IF NOT EXISTS team_connection (
604
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
605
+ source_teamfile TEXT NOT NULL,
606
+ target_name TEXT NOT NULL,
607
+ target_teamfile TEXT NOT NULL,
608
+ created_at TEXT NOT NULL,
609
+ UNIQUE(source_teamfile, target_name)
610
+ );
611
+
612
+ -- One row per request->response exchange
613
+ CREATE TABLE IF NOT EXISTS team_chat_log (
614
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
615
+ root_teamfile TEXT NOT NULL,
616
+ sender_teamfile TEXT NOT NULL,
617
+ sender_alias TEXT NOT NULL,
618
+ receiver_teamfile TEXT NOT NULL,
619
+ receiver_alias TEXT NOT NULL,
620
+ message TEXT NOT NULL,
621
+ response TEXT,
622
+ status TEXT NOT NULL,
623
+ error_detail TEXT,
624
+ truncated INTEGER NOT NULL DEFAULT 0,
625
+ created_at TEXT NOT NULL
626
+ );
627
+ CREATE INDEX IF NOT EXISTS idx_team_chat_log_root_time
628
+ ON team_chat_log(root_teamfile, created_at);
629
+
630
+ -- Per-mailbox durable watermark for daemon team_chat_log pushes. First-ever
631
+ -- start seeds this at current MAX(team_chat_log.id) to skip historic backlog;
632
+ -- subsequent restarts resume from the last successfully published id.
633
+ CREATE TABLE IF NOT EXISTS team_chat_log_cursor (
634
+ cursor_key TEXT PRIMARY KEY,
635
+ last_pushed_id INTEGER NOT NULL,
636
+ updated_at TEXT NOT NULL
637
+ );
638
+
639
+ -- v36: client-side S3 usage metering — per-device monthly op counts for quota.
640
+ CREATE TABLE IF NOT EXISTS usage_meter (
641
+ user_id TEXT NOT NULL,
642
+ device_id TEXT NOT NULL,
643
+ period TEXT NOT NULL,
644
+ put_count INTEGER NOT NULL DEFAULT 0,
645
+ get_count INTEGER NOT NULL DEFAULT 0,
646
+ list_count INTEGER NOT NULL DEFAULT 0,
647
+ updated_at TEXT NOT NULL,
648
+ PRIMARY KEY(user_id, device_id, period)
649
+ );
650
+
651
+ -- Per-receiver in-flight marker for single-sender serialization (§5)
652
+ CREATE TABLE IF NOT EXISTS agent_inflight (
653
+ receiver_teamfile TEXT NOT NULL,
654
+ receiver_alias TEXT NOT NULL,
655
+ sender_teamfile TEXT NOT NULL,
656
+ sender_alias TEXT NOT NULL,
657
+ started_at TEXT NOT NULL,
658
+ active_count INTEGER NOT NULL DEFAULT 1,
659
+ PRIMARY KEY(receiver_teamfile, receiver_alias)
660
+ );
@@ -0,0 +1,19 @@
1
+ {
2
+ "rules": [
3
+ {
4
+ "command": "gh",
5
+ "argv_patterns": ["pr create", "pr comment", "pr close", "pr review", "pr edit", "pr merge"],
6
+ "kind": "github_pr_mutation"
7
+ },
8
+ {
9
+ "command": "gh",
10
+ "argv_patterns": ["issue comment", "issue close", "issue edit"],
11
+ "kind": "github_issue_mutation"
12
+ },
13
+ {
14
+ "command": "git",
15
+ "argv_patterns": ["push"],
16
+ "kind": "git_push"
17
+ }
18
+ ]
19
+ }