From 7b35f4dc5f1133a07ee0137606bdc971d3f17fb7 Mon Sep 17 00:00:00 2001 From: kurihada Date: Thu, 19 Mar 2026 02:55:41 +0800 Subject: [PATCH] Add initial Go CLI skeleton --- cmd/inbox/main.go | 13 +++++++ cmd/orch/main.go | 13 +++++++ go.mod | 23 ++++++++++++ go.sum | 59 ++++++++++++++++++++++++++++++ internal/cli/inbox/init.go | 46 +++++++++++++++++++++++ internal/cli/inbox/root.go | 28 ++++++++++++++ internal/cli/orch/root.go | 26 +++++++++++++ internal/cli/orch/run.go | 20 ++++++++++ internal/db/migrate.go | 50 +++++++++++++++++++++++++ internal/db/open.go | 38 +++++++++++++++++++ internal/db/pragmas.go | 23 ++++++++++++ internal/db/schema/001_inbox.sql | 51 ++++++++++++++++++++++++++ internal/db/schema/002_orch.sql | 52 ++++++++++++++++++++++++++ internal/db/schema/003_events.sql | 18 +++++++++ internal/db/schema/004_council.sql | 45 +++++++++++++++++++++++ internal/protocol/json.go | 28 ++++++++++++++ internal/store/doc.go | 3 ++ 17 files changed, 536 insertions(+) create mode 100644 cmd/inbox/main.go create mode 100644 cmd/orch/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cli/inbox/init.go create mode 100644 internal/cli/inbox/root.go create mode 100644 internal/cli/orch/root.go create mode 100644 internal/cli/orch/run.go create mode 100644 internal/db/migrate.go create mode 100644 internal/db/open.go create mode 100644 internal/db/pragmas.go create mode 100644 internal/db/schema/001_inbox.sql create mode 100644 internal/db/schema/002_orch.sql create mode 100644 internal/db/schema/003_events.sql create mode 100644 internal/db/schema/004_council.sql create mode 100644 internal/protocol/json.go create mode 100644 internal/store/doc.go diff --git a/cmd/inbox/main.go b/cmd/inbox/main.go new file mode 100644 index 0000000..0679c6b --- /dev/null +++ b/cmd/inbox/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "os" + + inboxcli "ai-workflow-skill/internal/cli/inbox" +) + +func main() { + if err := inboxcli.NewRootCmd().Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmd/orch/main.go b/cmd/orch/main.go new file mode 100644 index 0000000..48b2ffe --- /dev/null +++ b/cmd/orch/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "os" + + orchcli "ai-workflow-skill/internal/cli/orch" +) + +func main() { + if err := orchcli.NewRootCmd().Execute(); err != nil { + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..698f24a --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module ai-workflow-skill + +go 1.26 + +require ( + github.com/spf13/cobra v1.10.1 + modernc.org/sqlite v1.40.1 +) + +require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/spf13/pflag v1.0.9 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/sys v0.36.0 // indirect + modernc.org/libc v1.66.10 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..91c8044 --- /dev/null +++ b/go.sum @@ -0,0 +1,59 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= +modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= +modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= +modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY= +modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/cli/inbox/init.go b/internal/cli/inbox/init.go new file mode 100644 index 0000000..08519fa --- /dev/null +++ b/internal/cli/inbox/init.go @@ -0,0 +1,46 @@ +package inbox + +import ( + "fmt" + + "ai-workflow-skill/internal/db" + "ai-workflow-skill/internal/protocol" + + "github.com/spf13/cobra" +) + +func newInitCmd(opts *rootOptions) *cobra.Command { + return &cobra.Command{ + Use: "init", + Short: "Initialize the shared SQLite database schema", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + sqlDB, err := db.Open(ctx, opts.dbPath) + if err != nil { + return err + } + defer sqlDB.Close() + + if err := db.ApplyMigrations(ctx, sqlDB); err != nil { + return err + } + + resp := protocol.Success{ + OK: true, + Command: "init", + Data: map[string]any{ + "db_path": opts.dbPath, + "status": "initialized", + }, + } + + if opts.json { + return protocol.WriteJSON(cmd.OutOrStdout(), resp) + } + + _, err = fmt.Fprintf(cmd.OutOrStdout(), "initialized database: %s\n", opts.dbPath) + return err + }, + } +} diff --git a/internal/cli/inbox/root.go b/internal/cli/inbox/root.go new file mode 100644 index 0000000..a04de5a --- /dev/null +++ b/internal/cli/inbox/root.go @@ -0,0 +1,28 @@ +package inbox + +import ( + "github.com/spf13/cobra" +) + +type rootOptions struct { + dbPath string + json bool + agent string +} + +func NewRootCmd() *cobra.Command { + opts := &rootOptions{} + + cmd := &cobra.Command{ + Use: "inbox", + Short: "Worker-facing durable coordination bus", + } + + cmd.PersistentFlags().StringVar(&opts.dbPath, "db", ".agents/coord.db", "SQLite database path") + cmd.PersistentFlags().BoolVar(&opts.json, "json", false, "Emit machine-readable JSON") + cmd.PersistentFlags().StringVar(&opts.agent, "agent", "", "Agent identity") + + cmd.AddCommand(newInitCmd(opts)) + + return cmd +} diff --git a/internal/cli/orch/root.go b/internal/cli/orch/root.go new file mode 100644 index 0000000..82efd7f --- /dev/null +++ b/internal/cli/orch/root.go @@ -0,0 +1,26 @@ +package orch + +import ( + "github.com/spf13/cobra" +) + +type rootOptions struct { + dbPath string + json bool +} + +func NewRootCmd() *cobra.Command { + opts := &rootOptions{} + + cmd := &cobra.Command{ + Use: "orch", + Short: "Leader-facing scheduler and control plane", + } + + cmd.PersistentFlags().StringVar(&opts.dbPath, "db", ".agents/coord.db", "SQLite database path") + cmd.PersistentFlags().BoolVar(&opts.json, "json", false, "Emit machine-readable JSON") + + cmd.AddCommand(newRunCmd()) + + return cmd +} diff --git a/internal/cli/orch/run.go b/internal/cli/orch/run.go new file mode 100644 index 0000000..96f7abf --- /dev/null +++ b/internal/cli/orch/run.go @@ -0,0 +1,20 @@ +package orch + +import "github.com/spf13/cobra" + +func newRunCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "run", + Short: "Run management commands", + } + + cmd.AddCommand(&cobra.Command{ + Use: "init", + Short: "Stub for future run initialization", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + }) + + return cmd +} diff --git a/internal/db/migrate.go b/internal/db/migrate.go new file mode 100644 index 0000000..1cf8e11 --- /dev/null +++ b/internal/db/migrate.go @@ -0,0 +1,50 @@ +package db + +import ( + "context" + "database/sql" + "embed" + "fmt" + "sort" +) + +//go:embed schema/*.sql +var schemaFS embed.FS + +func ApplyMigrations(ctx context.Context, db *sql.DB) error { + files, err := schemaFS.ReadDir("schema") + if err != nil { + return fmt.Errorf("read embedded schema directory: %w", err) + } + + names := make([]string, 0, len(files)) + for _, file := range files { + if file.IsDir() { + continue + } + names = append(names, file.Name()) + } + sort.Strings(names) + + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("begin schema transaction: %w", err) + } + defer tx.Rollback() + + for _, name := range names { + content, err := schemaFS.ReadFile("schema/" + name) + if err != nil { + return fmt.Errorf("read embedded schema file %q: %w", name, err) + } + if _, err := tx.ExecContext(ctx, string(content)); err != nil { + return fmt.Errorf("apply schema file %q: %w", name, err) + } + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("commit schema transaction: %w", err) + } + + return nil +} diff --git a/internal/db/open.go b/internal/db/open.go new file mode 100644 index 0000000..401a937 --- /dev/null +++ b/internal/db/open.go @@ -0,0 +1,38 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + "os" + "path/filepath" + + _ "modernc.org/sqlite" +) + +func Open(ctx context.Context, dbPath string) (*sql.DB, error) { + if err := ensureParentDir(dbPath); err != nil { + return nil, err + } + + db, err := sql.Open("sqlite", dbPath) + if err != nil { + return nil, fmt.Errorf("open sqlite database: %w", err) + } + + if err := applyPragmas(ctx, db); err != nil { + _ = db.Close() + return nil, err + } + + return db, nil +} + +func ensureParentDir(dbPath string) error { + parent := filepath.Dir(dbPath) + if parent == "." || parent == "" { + return nil + } + + return os.MkdirAll(parent, 0o755) +} diff --git a/internal/db/pragmas.go b/internal/db/pragmas.go new file mode 100644 index 0000000..a6efc84 --- /dev/null +++ b/internal/db/pragmas.go @@ -0,0 +1,23 @@ +package db + +import ( + "context" + "database/sql" + "fmt" +) + +func applyPragmas(ctx context.Context, db *sql.DB) error { + pragmas := []string{ + "PRAGMA foreign_keys = ON;", + "PRAGMA journal_mode = WAL;", + "PRAGMA busy_timeout = 5000;", + } + + for _, pragma := range pragmas { + if _, err := db.ExecContext(ctx, pragma); err != nil { + return fmt.Errorf("apply pragma %q: %w", pragma, err) + } + } + + return nil +} diff --git a/internal/db/schema/001_inbox.sql b/internal/db/schema/001_inbox.sql new file mode 100644 index 0000000..6ebf2b2 --- /dev/null +++ b/internal/db/schema/001_inbox.sql @@ -0,0 +1,51 @@ +CREATE TABLE IF NOT EXISTS threads ( + thread_id TEXT PRIMARY KEY, + run_id TEXT NOT NULL, + task_id TEXT NOT NULL, + subject TEXT NOT NULL, + created_by TEXT NOT NULL, + assigned_to TEXT NOT NULL, + status TEXT NOT NULL, + priority TEXT NOT NULL DEFAULT 'normal', + latest_message_id TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS messages ( + message_id TEXT PRIMARY KEY, + thread_id TEXT NOT NULL, + from_agent TEXT NOT NULL, + to_agent TEXT NOT NULL, + kind TEXT NOT NULL, + summary TEXT NOT NULL, + body TEXT NOT NULL DEFAULT '', + payload_json TEXT NOT NULL DEFAULT '{}', + created_at TEXT NOT NULL, + FOREIGN KEY(thread_id) REFERENCES threads(thread_id) +); + +CREATE TABLE IF NOT EXISTS leases ( + thread_id TEXT PRIMARY KEY, + agent_id TEXT NOT NULL, + lease_token TEXT NOT NULL, + claimed_at TEXT NOT NULL, + expires_at TEXT NOT NULL, + released_at TEXT +); + +CREATE TABLE IF NOT EXISTS artifacts ( + artifact_id TEXT PRIMARY KEY, + message_id TEXT NOT NULL, + path TEXT NOT NULL, + kind TEXT NOT NULL, + metadata_json TEXT NOT NULL DEFAULT '{}', + created_at TEXT NOT NULL, + FOREIGN KEY(message_id) REFERENCES messages(message_id) +); + +CREATE INDEX IF NOT EXISTS idx_threads_status_assigned + ON threads(status, assigned_to, updated_at); + +CREATE INDEX IF NOT EXISTS idx_messages_thread_created + ON messages(thread_id, created_at); diff --git a/internal/db/schema/002_orch.sql b/internal/db/schema/002_orch.sql new file mode 100644 index 0000000..e0cfeaf --- /dev/null +++ b/internal/db/schema/002_orch.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS runs ( + run_id TEXT PRIMARY KEY, + goal TEXT NOT NULL, + summary TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'active', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS tasks ( + run_id TEXT NOT NULL, + task_id TEXT NOT NULL, + title TEXT NOT NULL, + summary TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL, + default_to TEXT, + priority TEXT NOT NULL DEFAULT 'normal', + acceptance_json TEXT NOT NULL DEFAULT '[]', + latest_attempt_no INTEGER, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (run_id, task_id), + FOREIGN KEY(run_id) REFERENCES runs(run_id) +); + +CREATE TABLE IF NOT EXISTS task_dependencies ( + run_id TEXT NOT NULL, + task_id TEXT NOT NULL, + depends_on_task_id TEXT NOT NULL, + PRIMARY KEY (run_id, task_id, depends_on_task_id) +); + +CREATE TABLE IF NOT EXISTS task_attempts ( + run_id TEXT NOT NULL, + task_id TEXT NOT NULL, + attempt_no INTEGER NOT NULL, + assigned_to TEXT NOT NULL, + thread_id TEXT NOT NULL, + base_ref TEXT, + base_commit TEXT, + branch_name TEXT, + worktree_path TEXT, + workspace_status TEXT, + result_commit TEXT, + status TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (run_id, task_id, attempt_no) +); + +CREATE INDEX IF NOT EXISTS idx_tasks_run_status + ON tasks(run_id, status, priority, updated_at); diff --git a/internal/db/schema/003_events.sql b/internal/db/schema/003_events.sql new file mode 100644 index 0000000..f1ceb3a --- /dev/null +++ b/internal/db/schema/003_events.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS events ( + event_id INTEGER PRIMARY KEY AUTOINCREMENT, + run_id TEXT NOT NULL, + task_id TEXT NOT NULL, + thread_id TEXT, + source TEXT NOT NULL, + event_type TEXT NOT NULL, + message_id TEXT, + summary TEXT NOT NULL DEFAULT '', + payload_json TEXT NOT NULL DEFAULT '{}', + created_at TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_events_run_event + ON events(run_id, event_id); + +CREATE INDEX IF NOT EXISTS idx_events_thread_event + ON events(thread_id, event_id); diff --git a/internal/db/schema/004_council.sql b/internal/db/schema/004_council.sql new file mode 100644 index 0000000..21695c8 --- /dev/null +++ b/internal/db/schema/004_council.sql @@ -0,0 +1,45 @@ +CREATE TABLE IF NOT EXISTS council_runs ( + run_id TEXT PRIMARY KEY, + mode TEXT NOT NULL, + target_type TEXT NOT NULL, + output_mode TEXT NOT NULL, + only_unanimous INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS council_reviewers ( + run_id TEXT NOT NULL, + reviewer_role TEXT NOT NULL, + task_id TEXT NOT NULL, + status TEXT NOT NULL, + PRIMARY KEY (run_id, reviewer_role) +); + +CREATE TABLE IF NOT EXISTS council_findings ( + run_id TEXT NOT NULL, + reviewer_role TEXT NOT NULL, + finding_id TEXT NOT NULL, + title TEXT NOT NULL, + summary TEXT NOT NULL, + proposal TEXT NOT NULL, + rationale TEXT NOT NULL, + confidence TEXT NOT NULL, + tags_json TEXT NOT NULL DEFAULT '[]', + target_refs_json TEXT NOT NULL DEFAULT '{}', + PRIMARY KEY (run_id, reviewer_role, finding_id) +); + +CREATE TABLE IF NOT EXISTS council_groups ( + run_id TEXT NOT NULL, + group_id TEXT NOT NULL, + proposal TEXT NOT NULL, + bucket TEXT NOT NULL, + support_count INTEGER NOT NULL, + supporters_json TEXT NOT NULL DEFAULT '[]', + dissenters_json TEXT NOT NULL DEFAULT '[]', + rationale_summary TEXT NOT NULL DEFAULT '', + tags_json TEXT NOT NULL DEFAULT '[]', + source_finding_ids_json TEXT NOT NULL DEFAULT '[]', + PRIMARY KEY (run_id, group_id) +); diff --git a/internal/protocol/json.go b/internal/protocol/json.go new file mode 100644 index 0000000..9cb20db --- /dev/null +++ b/internal/protocol/json.go @@ -0,0 +1,28 @@ +package protocol + +import ( + "encoding/json" + "io" +) + +type Success struct { + OK bool `json:"ok"` + Command string `json:"command"` + Data map[string]any `json:"data,omitempty"` +} + +type Error struct { + OK bool `json:"ok"` + Error ErrorPayload `json:"error"` +} + +type ErrorPayload struct { + Code string `json:"code"` + Message string `json:"message"` +} + +func WriteJSON(w io.Writer, v any) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(v) +} diff --git a/internal/store/doc.go b/internal/store/doc.go new file mode 100644 index 0000000..bbd69c5 --- /dev/null +++ b/internal/store/doc.go @@ -0,0 +1,3 @@ +package store + +// Package store contains higher-level database access helpers.