원클릭으로
sqlite-provider
SQLite-specific provider implementation for waggle. Loaded when the active provider is sqlite.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
SQLite-specific provider implementation for waggle. Loaded when the active provider is sqlite.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Creates, updates, deletes, and queries tasks in the configured data source. Handles task creation with required confirmations, state transitions, "next task" recommendations, and personal task dashboard. Use this skill for ANY task-related request — create, update, delete, query, list, filter by status/priority/assignee, mark done, block, assign to self, change priority, view personal dashboards, or ask "what's next". Also handles task dependencies and links: block, unblock, blocked by, blocking, dependency/dependencies, parent task, subtask, child task, link tasks, relation, connect tasks. Also scoped queries such as "tasks for <store/project>" or "filter tasks by store/project". If the user mentions tasks in any way, use this skill. Invoke this skill for any Tasks DB write — do not call `notion-create-pages`, `notion-update-page`, or `notion-update-relation` on task pages directly, even for single-field edits. Direct writes skip quality gates (AC/EP rubric, executor invariants, Acknowledged At auto-set, sub
Deterministic field validation for task status transitions. Returns pass/fail with errors and warnings as JSON. Used by managing-tasks, executing-tasks, and running-daily-tasks.
Renders task data as interactive HTML dashboards (Kanban, List, Calendar, Gantt). In cli / claude-desktop the dashboard is served by a local view server on localhost:3456. In Cowork the dashboard is registered as a Live Artifact that fetches Notion data directly via window.cowork.callMcpTool. Use this skill whenever the user wants to see tasks visually — board views, timelines, calendars, or any kind of task visualization or dashboard display. Read-only views only. For any write, or a scoped filter that may lead to a mutation, route through `managing-tasks` instead. Triggers on: "kanban", "list view", "show tasks", "view", "visualize", "gantt", "calendar", "board", "timeline", "display tasks", "open dashboard".
Notion-specific provider implementation for waggle. Loaded when the active provider is notion.
Set up Notion as the waggle provider. Trigger phrases - "setup notion", "configure notion for waggle".
Turso-specific implementation for waggle. Loaded by detecting-provider when active_provider is turso.
| name | sqlite-provider |
| description | SQLite-specific provider implementation for waggle. Loaded when the active provider is sqlite. |
| user-invocable | false |
This file contains all SQLite-specific implementation details for waggle. Load this file when the active provider is sqlite.
Silent operation: This skill runs as an internal step of an invoking skill. Return results to the invoking flow without user-facing narration — the caller owns all user communication. Only errors, warnings, and prompts required to proceed may surface directly.
When detecting-provider requests config retrieval for the SQLite provider:
WAGGLE_SQLITE_DB_PATH environment variable. If not set, default to ~/.waggle/tasks.db.headless_config session variable:
dbPath — the resolved path from step 1teamsDatabaseExists (optional — true if teams table has rows)sprintsDatabaseExists (optional — true if sprints table has rows)After loading config, verify the database exists and has the correct schema:
sqlite3 "<dbPath>" ".tables"
Expected tables: tasks, task_dependencies, teams, sprints, intake_log.
If any table is missing, run the init script to auto-repair:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/init-db.sh "<dbPath>"
init-db.sh also migrates column additions on an already-initialized database (CREATE TABLE IF NOT EXISTS does not alter an existing table). It runs an idempotent, pragma_table_info-guarded ALTER TABLE ... ADD COLUMN for newer columns such as attachments (the Attachments extended field), so re-running it on any existing DB is safe and a no-op once present.
Precondition (v2.8.1+): Before invoking the INSERT below, verify that the session-resolved current_user.id is not the fallback sentinel "unknown". If it is, halt and surface an error to the caller:
Cannot create task: current_user.id is "unknown". Configure proper identity resolution before retrying — see the Identity Resolution section below. The simplest fix is to ensure
$USERis set in the shell environment, or setWAGGLE_USER_IDexplicitly.
This enforces the protocol's "no anonymous tasks" rule. The Identity Resolution section (below) is structured so that id resolves to a real value ($WAGGLE_USER_ID → $USER → "unknown") on every supported environment, so this halt should rarely fire in practice — it catches genuinely unconfigured environments (an unset $USER with no override).
sqlite3 "<dbPath>" "INSERT INTO tasks (title, description, acceptance_criteria, status, priority, executor, requires_review, execution_plan, working_directory, assignee, issuer) VALUES ('<title>', '<description>', '<criteria>', '<status>', '<priority>', '<executor>', <0|1>, '<plan>', '<dir>', '<assignee_json>', '${current_user.id}'); SELECT last_insert_rowid();"
The issuer column receives ${current_user.id} directly from the substituted session variable. The caller does NOT pass an explicit Issuer — per the protocol's Issuer Auto-Populate Contract, Issuer is provider-managed.
To get the generated ID with the minimum required fields, use:
sqlite3 "<dbPath>" "INSERT INTO tasks (title, status, issuer) VALUES ('<title>', 'Backlog', '${current_user.id}') RETURNING id;"
IMPORTANT:
' -> ''.${current_user.id} if the resolved value can contain quotes (it should not — $USER-derived strings and email addresses are quote-safe by construction, but defensive escaping is recommended).sqlite3 "<dbPath>" "UPDATE tasks SET <field> = '<value>', updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = '<task_id>';"
For multiple fields:
sqlite3 "<dbPath>" "UPDATE tasks SET status = '<status>', agent_output = '<output>', updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = '<task_id>';"
tags, assignee, and attachments are stored as JSON-array text. For attachments, set a JSON array of file descriptors — this provider does not host files (supportsFileHosting=false), so each url must be an externally-hosted, caller-supplied URL:
sqlite3 "<dbPath>" "UPDATE tasks SET attachments = '[{\"url\":\"https://files.example.com/spec.pdf\",\"name\":\"spec.pdf\",\"mime_type\":\"application/pdf\"}]', updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = '<task_id>';"
sqlite3 -json "<dbPath>" "SELECT t.*, GROUP_CONCAT(td.blocked_by_id) as blocked_by_ids FROM tasks t LEFT JOIN task_dependencies td ON t.id = td.task_id WHERE t.id = '<task_id>' GROUP BY t.id;"
sqlite3 "<dbPath>" "DELETE FROM tasks WHERE id = '<task_id>';"
Dependencies are automatically removed via ON DELETE CASCADE.
Add dependency:
sqlite3 "<dbPath>" "INSERT OR IGNORE INTO task_dependencies (task_id, blocked_by_id) VALUES ('<task_id>', '<blocker_id>');"
Remove dependency:
sqlite3 "<dbPath>" "DELETE FROM task_dependencies WHERE task_id = '<task_id>' AND blocked_by_id = '<blocker_id>';"
Use the query script for filtered queries with JSON output:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh \
"<dbPath>" '<where_clause>' '<order_clause>'
All tasks (no filter):
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>"
Ready tasks:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.status = 'Ready'"
Tasks by executor and status (single executor):
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.status = 'Ready' AND t.executor = 'cowork'"
Tasks by executor and status (multiple executors — for cli/claude-desktop environments):
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.status = 'Ready' AND t.executor IN ('cli','claude-desktop','cowork')"
Tasks assigned to current user:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.assignee LIKE '%<user_id>%'"
Tasks owned by user via Assignee OR Issuer fallback (v2.8.1+):
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" \
"(t.assignee LIKE '%<user_id>%' OR (t.issuer = '<user_id>' AND (t.assignee IS NULL OR t.assignee = '' OR t.assignee = '[]')))"
Note that t.issuer is a single-value TEXT column (not a JSON array), so it uses = for exact match against <user_id>. This is the SQLite equivalent of the Notion filter Issuer.created_by:{contains:<user_id>}.
In Progress tasks (for concurrency check):
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.status = 'In Progress' AND t.assignee LIKE '%<user_id>%'"
Sort by Priority then Due Date:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "" \
"CASE t.priority WHEN 'Urgent' THEN 1 WHEN 'High' THEN 2 WHEN 'Medium' THEN 3 WHEN 'Low' THEN 4 END ASC, t.due_date ASC"
Subtasks of a parent:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.parent_task_id = '<parent_task_id>'"
Check if a task has children:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.parent_task_id = '<task_id>'" | jq '.results | length'
Check if a candidate parent is itself a subtask:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" "t.id = '<candidate_parent_id>'" | jq '.results[0].parent_task_id'
If the result is non-null, the candidate is already a subtask and cannot be used as a parent (2-level limit).
blocked_by array is empty OR query each blocked_by task and confirm all have status = 'Done'.bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" '<where>' '<order>' | \
jq '[.results[] | {id, title, status, priority, executor, assignee, due_date, blocked_by: (.blocked_by | length | tostring) + " deps"}]'
When referring to a task in dispatch prompts and completion instructions, use:
id columnsqlite3 <dbPath> \"UPDATE tasks SET agent_output = '<result>', status = 'Done', updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = '<task_id>';\""The following template is injected into dispatch prompts by executing-tasks. Placeholders are resolved at dispatch time.
Task ID: <task_id>
Database path: <db_path>
On completion:
1. Run: sqlite3 "<db_path>" "UPDATE tasks SET agent_output='<result>', status='Done', updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now') WHERE id='<task_id>';"
- If Requires Review = ON: set status to 'In Review' instead of 'Done'
2. On error: sqlite3 "<db_path>" "UPDATE tasks SET error_message='<error>', status='Blocked', updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now') WHERE id='<task_id>';"
After any task operation (create, update, delete), push fresh data to the local view server:
bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>"
TASKS_JSON=$(bash ${CLAUDE_PLUGIN_ROOT}/skills/sqlite-provider/scripts/query-tasks.sh "<dbPath>" | jq -c '{tasks: [.results[] | {
id, title, description, acceptanceCriteria: .acceptance_criteria, status, blockedBy: .blocked_by,
priority, executor, requiresReview: .requires_review, executionPlan: .execution_plan,
workingDirectory: .working_directory, sessionReference: .session_reference,
dispatchedAt: .dispatched_at, agentOutput: .agent_output, errorMessage: .error_message,
context, artifacts, repository, startDate: .start_date, dueDate: .due_date, tags, parentTaskId: .parent_task_id,
project, team, assignee, attachments, issuer, url: "", sprintId: .sprint_id, sprintName: null,
complexityScore: .complexity_score, backlogOrder: .backlog_order
}], updatedAt: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))}')
curl -s http://localhost:3456/api/health -o /dev/null 2>/dev/null && \
curl -s -X POST http://localhost:3456/api/data \
-H "Content-Type: application/json" -d "$TASKS_JSON" -o /dev/null 2>/dev/null || true
| SQLite Column | TasksResponse Field |
|---|---|
| id | id |
| title | title |
| description | description |
| acceptance_criteria | acceptanceCriteria |
| status | status |
| blocked_by (via task_dependencies) | blockedBy |
| priority | priority |
| executor | executor |
| requires_review | requiresReview (boolean) |
| execution_plan | executionPlan |
| working_directory | workingDirectory |
| session_reference | sessionReference |
| dispatched_at | dispatchedAt |
| agent_output | agentOutput |
| error_message | errorMessage |
| context | context |
| artifacts | artifacts |
| repository | repository |
| start_date | startDate |
| due_date | dueDate |
| tags | tags (JSON array) |
| parent_task_id | parentTaskId |
| project | project |
| team | team |
| assignee | assignee (JSON array) |
| attachments | attachments (JSON array of {url, name, mime_type?, size?}; supportsFileHosting=false — externally-hosted URLs only) |
| issuer | issuer (single user ID string; auto-populated by Create Task template, v2.8.1+) |
| (empty string) | url |
| sprint_id | sprintId |
| complexity_score | complexityScore |
| backlog_order | backlogOrder |
Called by resolving-identity shared skill when active_provider = sqlite.
SQLite is local — no remote user system. Identity is derived from the shell environment so that multi-user machines and CI environments produce distinct user IDs.
Resolution order:
WAGGLE_USER_ID env var is set and non-empty → use it. This is the override path for environments where $USER is not meaningful (CI runners, shared service accounts, automation).$USER env var is set and non-empty → use it. On Linux / macOS / WSL this gives a per-user shell account name that is unique on the machine. (v2.8.1+: previously this populated only name; now it also populates id.)id ← "unknown". This sentinel signals "identity is genuinely unresolvable" and triggers the Create Task precondition halt.Concretely set:
id ← $WAGGLE_USER_ID if non-empty, else $USER if non-empty, else "unknown"name ← $USER env var or "unknown"email ← nullNote (v2.8.1+): The Create Task precondition halts only when id == "unknown". The literal "local" is no longer used as a fallback — using $USER directly gives a real identifier on every supported environment, eliminating the "every task is owned by 'local'" failure mode.
If teams table has rows:
sqlite3 -json "<dbPath>" "SELECT * FROM teams;"current_user.namecurrent_user.teams and current_team per the same logic as other providersSQLite is local — return members from teams table if available, otherwise org_members: [].
sqlite3 -json "<dbPath>" "SELECT members FROM teams;" | jq '[.[].members | fromjson | .[] ] | unique_by(.name)'
| Error Category | Condition | Action |
|---|---|---|
| Database locked | SQLITE_BUSY | Retryable — wait 1-2 seconds and retry, max 3 attempts |
| File not found | DB path does not exist | Terminal — instruct user to run setting-up-tasks |
| Schema mismatch | Missing table or column | Auto-repair — run init-db.sh to create missing tables |