Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "genie",
"version": "3.260318.7",
"version": "3.260319.1",
"source": "./plugins/genie",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
}
Expand Down
41 changes: 41 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "genie",
"name": "Genie",
"description": "Skills, agents, and hooks for the Genie CLI terminal orchestration toolkit",
"version": "3.260318.7",
"version": "3.260319.1",
"configSchema": {
"type": "object",
"additionalProperties": false,
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@automagik/genie",
"version": "3.260318.7",
"version": "3.260319.1",
"description": "Collaborative terminal toolkit for human + AI workflows",
"type": "module",
"bin": {
Expand All @@ -26,6 +26,8 @@
"@inquirer/prompts": "^7.0.0",
"commander": "^12.1.0",
"js-yaml": "^4.1.1",
"pgserve": "^1.1.6",
"postgres": "^3.4.8",
"uuid": "^11.1.0",
"zod": "^3.25.0"
},
Expand Down Expand Up @@ -54,5 +56,5 @@
"publishConfig": {
"access": "public"
},
"trustedDependencies": ["@biomejs/biome"]
"trustedDependencies": ["@biomejs/biome", "bun"]
}
2 changes: 1 addition & 1 deletion plugins/genie/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genie",
"version": "3.260318.7",
"version": "3.260319.1",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
"author": {
"name": "Namastex Labs"
Expand Down
2 changes: 1 addition & 1 deletion plugins/genie/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genie-plugin",
"version": "3.260318.7",
"version": "3.260319.1",
"private": true,
"description": "Runtime dependencies for genie bundled CLIs",
"type": "module",
Expand Down
131 changes: 131 additions & 0 deletions src/db/migrations/001_initial.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
-- 001_initial.sql — Core genie schema
-- Tables: schedules, triggers, runs, heartbeats, audit_events, agent_checkpoints

-- ============================================================================
-- Schedules — cron-like schedule definitions
-- ============================================================================
CREATE TABLE schedules (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
cron_expression TEXT NOT NULL,
timezone TEXT DEFAULT 'UTC',
command TEXT,
metadata JSONB DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'paused', 'deleted')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_schedules_status ON schedules(status);
CREATE INDEX idx_schedules_name ON schedules(name);

Comment on lines +7 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/db/migrations/001_initial.sql

Repository: automagik-dev/genie

Length of output: 6048


🏁 Script executed:

cat -n src/term-commands/schedule.ts | head -100

Repository: automagik-dev/genie

Length of output: 3616


🏁 Script executed:

rg -A 5 "name-based\|by.*name\|WHERE.*name" src/term-commands/schedule.ts

Repository: automagik-dev/genie

Length of output: 45


🏁 Script executed:

rg "WHERE\|SELECT.*schedules\|cancel\|retry\|history" src/term-commands/schedule.ts -A 3 -B 1

Repository: automagik-dev/genie

Length of output: 45


🏁 Script executed:

rg "UPDATE.*schedules\|UPDATE.*triggers" src/term-commands/schedule.ts -A 3 -B 1

Repository: automagik-dev/genie

Length of output: 45


🏁 Script executed:

cat -n src/term-commands/schedule.ts | grep -A 50 "cancel\|retry\|history" | head -100

Repository: automagik-dev/genie

Length of output: 3511


🏁 Script executed:

cat -n src/term-commands/schedule.ts | sed -n '370,430p'

Repository: automagik-dev/genie

Length of output: 2353


🏁 Script executed:

rg "UPDATE triggers SET status" src/term-commands/schedule.ts -B 3 -A 3

Repository: automagik-dev/genie

Length of output: 253


🏁 Script executed:

cat -n src/term-commands/schedule.ts | sed -n '430,480p'

Repository: automagik-dev/genie

Length of output: 1804


🏁 Script executed:

rg "schedule create\|INSERT INTO schedules" src/term-commands/schedule.ts -B 5 -A 10

Repository: automagik-dev/genie

Length of output: 45


🏁 Script executed:

rg "async function scheduleCreateCommand\|INSERT INTO schedules" src/term-commands/schedule.ts -B 5 -A 15

Repository: automagik-dev/genie

Length of output: 45


🏁 Script executed:

cat -n src/term-commands/schedule.ts | sed -n '200,280p'

Repository: automagik-dev/genie

Length of output: 3464


Add partial unique index to enforce active schedule-name uniqueness in the database.

The application checks for duplicate active names before creating a schedule, but this check happens outside the transaction. Two concurrent creates can both pass the check and insert separate active rows with the same name, causing later cancel/history/retry calls by name to be ambiguous or operate on the wrong schedule. Add CREATE UNIQUE INDEX idx_schedules_active_name ON schedules(name) WHERE status = 'active' to enforce this at the schema level.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/migrations/001_initial.sql` around lines 7 - 22, Add a partial unique
index on schedules to prevent duplicate active names by creating a unique index
named idx_schedules_active_name on the name column where status = 'active'
(i.e., CREATE UNIQUE INDEX idx_schedules_active_name ON schedules(name) WHERE
status = 'active'); update the migration that defines the schedules table (table
name: schedules, existing indexes: idx_schedules_status and idx_schedules_name)
to include this new index and ensure the migration handles or documents any
pre-existing active-name duplicates before applying.

-- ============================================================================
-- Triggers — individual fire instances of a schedule
-- ============================================================================
CREATE TABLE triggers (
id TEXT PRIMARY KEY,
schedule_id TEXT NOT NULL REFERENCES schedules(id) ON DELETE CASCADE,
due_at TIMESTAMPTZ NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'executing', 'completed', 'failed', 'skipped')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ
);

CREATE INDEX idx_triggers_schedule_id ON triggers(schedule_id);
CREATE INDEX idx_triggers_status ON triggers(status);
CREATE INDEX idx_triggers_due_pending ON triggers(due_at)
WHERE status = 'pending';

-- ============================================================================
-- Runs — execution attempts for a trigger
-- ============================================================================
CREATE TABLE runs (
id TEXT PRIMARY KEY,
trigger_id TEXT NOT NULL REFERENCES triggers(id) ON DELETE CASCADE,
worker_id TEXT,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'leased', 'running', 'completed', 'failed', 'timeout', 'cancelled')),
output TEXT,
error TEXT,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_runs_trigger_id ON runs(trigger_id);
CREATE INDEX idx_runs_worker_id ON runs(worker_id);
CREATE INDEX idx_runs_status ON runs(status);
CREATE INDEX idx_runs_leased ON runs(status, started_at)
WHERE status IN ('leased', 'running');

-- ============================================================================
-- Heartbeats — worker liveness tracking
-- ============================================================================
CREATE TABLE heartbeats (
id TEXT PRIMARY KEY,
worker_id TEXT NOT NULL,
run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
status TEXT NOT NULL DEFAULT 'alive'
CHECK (status IN ('alive', 'idle', 'busy', 'dead')),
context JSONB DEFAULT '{}',
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_heartbeats_worker_id ON heartbeats(worker_id);
CREATE INDEX idx_heartbeats_run_id ON heartbeats(run_id);
CREATE INDEX idx_heartbeats_last_seen ON heartbeats(last_seen_at);

-- ============================================================================
-- Audit events — immutable log of state transitions
-- ============================================================================
CREATE TABLE audit_events (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
entity_type TEXT NOT NULL,
entity_id TEXT NOT NULL,
event_type TEXT NOT NULL,
actor TEXT,
details JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_audit_entity ON audit_events(entity_type, entity_id);
CREATE INDEX idx_audit_created ON audit_events(created_at);
CREATE INDEX idx_audit_actor ON audit_events(actor);

-- ============================================================================
-- Agent checkpoints — session resume state for agents
-- ============================================================================
CREATE TABLE agent_checkpoints (
worker_id TEXT PRIMARY KEY,
wish_slug TEXT,
group_name TEXT,
phase TEXT CHECK (phase IN ('executing', 'validating', 'reporting')),
context_summary TEXT,
dispatch_context TEXT,
branch_name TEXT,
last_checkpoint TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_checkpoints_wish ON agent_checkpoints(wish_slug);
CREATE INDEX idx_checkpoints_group ON agent_checkpoints(group_name);

-- ============================================================================
-- LISTEN/NOTIFY — real-time trigger-due notifications
-- ============================================================================
CREATE OR REPLACE FUNCTION notify_trigger_due()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('genie_trigger_due', NEW.id::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_notify_due
AFTER INSERT ON triggers
FOR EACH ROW
WHEN (NEW.status = 'pending')
EXECUTE FUNCTION notify_trigger_due();
Comment on lines +119 to +131

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "001_initial.sql" -o -name "*migrations*" -type d

Repository: automagik-dev/genie

Length of output: 118


🏁 Script executed:

wc -l ./src/db/migrations/001_initial.sql

Repository: automagik-dev/genie

Length of output: 102


🏁 Script executed:

cat -n ./src/db/migrations/001_initial.sql

Repository: automagik-dev/genie

Length of output: 6048


🏁 Script executed:

rg "status.*=.*['\"]pending['\"]|pending.*status" --type sql --type js --type ts -B 2 -A 2

Repository: automagik-dev/genie

Length of output: 2985


Add AFTER UPDATE trigger to notify when triggers transition back to pending.

trg_notify_due only fires on INSERT. The retry path in scheduler-daemon.ts (timeout recovery) and schedule.ts (manual re-queue) both update existing rows back to status = 'pending', but no notification is sent. The daemon must wait for the fallback poll instead of being notified immediately. Add an AFTER UPDATE branch that fires on transitions to pending status.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/migrations/001_initial.sql` around lines 119 - 131, The current
trigger trg_notify_due only fires AFTER INSERT; add an AFTER UPDATE branch that
calls the same notify_trigger_due() function when a row transitions back to
pending so updates that set status = 'pending' notify the daemon immediately.
Implement an UPDATE trigger on the triggers table with WHEN (OLD.status IS
DISTINCT FROM 'pending' AND NEW.status = 'pending') (or OLD.status <> 'pending'
AND NEW.status = 'pending') and EXECUTE FUNCTION notify_trigger_due(); keep the
existing INSERT trigger intact and reuse the notify_trigger_due() function and
trg_notify_due naming to group related behavior.

33 changes: 33 additions & 0 deletions src/db/migrations/002_scheduler_extensions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- 002_scheduler_extensions.sql — Scheduler daemon extensions
-- Adds lease columns to triggers, trace_id to runs, run_spec to schedules

-- ============================================================================
-- Schedules — add interval support and run specification
-- ============================================================================
ALTER TABLE schedules ADD COLUMN IF NOT EXISTS interval_ms BIGINT;
ALTER TABLE schedules ADD COLUMN IF NOT EXISTS run_spec JSONB DEFAULT '{}';

-- ============================================================================
-- Triggers — add lease columns and idempotency key
-- ============================================================================
ALTER TABLE triggers ADD COLUMN IF NOT EXISTS idempotency_key TEXT;
ALTER TABLE triggers ADD COLUMN IF NOT EXISTS leased_by TEXT;
ALTER TABLE triggers ADD COLUMN IF NOT EXISTS leased_until TIMESTAMPTZ;

CREATE UNIQUE INDEX IF NOT EXISTS idx_triggers_idempotency
ON triggers(idempotency_key)
WHERE idempotency_key IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_triggers_leased
ON triggers(status, leased_until)
WHERE status = 'executing';

-- ============================================================================
-- Runs — add trace_id and lease_timeout_ms
-- ============================================================================
ALTER TABLE runs ADD COLUMN IF NOT EXISTS trace_id TEXT;
ALTER TABLE runs ADD COLUMN IF NOT EXISTS lease_timeout_ms INTEGER DEFAULT 300000;
ALTER TABLE runs ADD COLUMN IF NOT EXISTS exit_code INTEGER;

CREATE INDEX IF NOT EXISTS idx_runs_trace_id ON runs(trace_id)
WHERE trace_id IS NOT NULL;
15 changes: 15 additions & 0 deletions src/db/migrations/003_machine_snapshots.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- 003_machine_snapshots.sql — Machine state snapshots for scheduler observability

CREATE TABLE IF NOT EXISTS machine_snapshots (
id TEXT PRIMARY KEY,
active_workers INTEGER NOT NULL DEFAULT 0,
active_teams INTEGER NOT NULL DEFAULT 0,
tmux_sessions INTEGER NOT NULL DEFAULT 0,
cpu_percent REAL,
memory_mb REAL,
context JSONB DEFAULT '{}',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

context column allows NULL despite having a DEFAULT.

The context column has DEFAULT '{}' but lacks NOT NULL, so explicit NULL inserts bypass the default. Other columns consistently use NOT NULL DEFAULT. If context should always be a valid JSONB object, add NOT NULL:

♻️ Add NOT NULL for consistency
-  context JSONB DEFAULT '{}',
+  context JSONB NOT NULL DEFAULT '{}',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
context JSONB DEFAULT '{}',
context JSONB NOT NULL DEFAULT '{}',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/migrations/003_machine_snapshots.sql` at line 10, The column
definition for context should be changed to enforce non-null JSONB values:
update the migration's column line from "context JSONB DEFAULT '{}'," to
"context JSONB NOT NULL DEFAULT '{}'," so explicit NULL inserts are rejected and
default applies consistently; update any corresponding schema/ORM model to match
if present.

created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_machine_snapshots_created
ON machine_snapshots(created_at);
Comment on lines +3 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

**Consider retention policy for snapshots.**Machine snapshots can accumulate without bounds. "Having partitions for different time spans makes it more efficient to drop/delete/expire old data." Consider adding a retention cleanup mechanism or documenting expected growth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db/migrations/003_machine_snapshots.sql` around lines 3 - 15, The
machine_snapshots table can grow unbounded; add a retention strategy for
machine_snapshots (e.g., time-based partitioning on created_at or a scheduled
cleanup) and implement a mechanism to expire old data: convert machine_snapshots
into range partitions by created_at (or create monthly/weekly partitions) and
add a scheduled job (pg_cron or a DB-side function + external scheduler) to drop
old partitions, or if partitioning is not desired add a cleanup function that
deletes rows older than a configurable retention period and schedule it to run
regularly; update related index idx_machine_snapshots_created and any queries
using created_at to work with the chosen approach and document the retention
configuration.

8 changes: 4 additions & 4 deletions src/genie-commands/__tests__/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ describe('buildClaudeCommand', () => {
expect(cmd).not.toContain('--resume');
});

test('with undefined continueName does NOT include --continue', () => {
test('with undefined continueName does NOT include --resume', () => {
const cmd = buildClaudeCommand('genie', undefined, undefined);
expect(cmd).not.toContain('--continue');
expect(cmd).not.toContain('--resume');
});

test('with a continueName DOES include --continue', () => {
test('with a continueName DOES include --resume', () => {
const cmd = buildClaudeCommand('my-team', undefined, 'my-team');
expect(cmd).toContain("--continue 'my-team'");
expect(cmd).toContain("--resume 'my-team'");
});

test('file path is passed directly, no content inlined', () => {
Expand Down
16 changes: 8 additions & 8 deletions src/genie-commands/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,11 @@ async function createSession(
}

/**
* Launch Claude Code in a tmux pane with --continue fallback.
* Launch Claude Code in a tmux pane with --resume fallback.
*
* Tries --continue first (preserves conversation history). Waits ~3 seconds,
* then checks #{pane_current_command} — if the pane shows a shell, --continue
* failed (no prior conversation) so we retry fresh without --continue.
* Tries --resume first (preserves conversation history). Waits ~3 seconds,
* then checks #{pane_current_command} — if the pane shows a shell, --resume
* failed (no prior session) so we retry fresh without --resume.
*/
async function launchWithContinueFallback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The function name launchWithContinueFallback is now outdated after refactoring from --continue to --resume. To improve code clarity and maintainability, please rename it to launchWithResumeFallback to match its updated functionality.

Suggested change
async function launchWithContinueFallback(
async function launchWithResumeFallback(

target: string,
Expand All @@ -209,17 +209,17 @@ async function launchWithContinueFallback(
const continueCmd = buildClaudeCommand(windowName, systemPromptFile || undefined, continueName);
const freshCmd = buildClaudeCommand(windowName, systemPromptFile || undefined, undefined);

// Try --continue first (preserves conversation history)
// Try --resume first (preserves conversation history)
await tmux.executeTmux(`send-keys -t ${shellQuote(target)} ${shellQuote(continueCmd)} Enter`);

// Wait briefly then check if CC is running or fell back to shell
// CC fails fast on missing conversations (~1s), 3s is conservative
// CC fails fast on missing sessions (~1s), 3s is conservative
await new Promise((r) => setTimeout(r, 3000));
const afterCmd = (await tmux.executeTmux(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim();

if (['bash', 'zsh', 'sh', 'fish'].includes(afterCmd)) {
// --continue failed, start fresh
console.log('No prior conversation found, starting fresh session...');
// --resume failed, start fresh
console.log('No prior session found, starting fresh session...');
await tmux.executeTmux(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`);
}
}
Expand Down
Loading
Loading