Add initial Go CLI skeleton

This commit is contained in:
2026-03-19 02:55:41 +08:00
parent 84bd4fd9a7
commit 7b35f4dc5f
17 changed files with 536 additions and 0 deletions
+46
View File
@@ -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
},
}
}
+28
View File
@@ -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
}
+26
View File
@@ -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
}
+20
View File
@@ -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
}
+50
View File
@@ -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
}
+38
View File
@@ -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)
}
+23
View File
@@ -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
}
+51
View File
@@ -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);
+52
View File
@@ -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);
+18
View File
@@ -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);
+45
View File
@@ -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)
);
+28
View File
@@ -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)
}
+3
View File
@@ -0,0 +1,3 @@
package store
// Package store contains higher-level database access helpers.