From waggle
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.
npx claudepluginhub kazukinagata/waggle --plugin waggleThis skill uses the workspace's default tool permissions.
This shared skill provides a deterministic bash+jq validation script for task status transitions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
This shared skill provides a deterministic bash+jq validation script for task status transitions. It enforces required fields as hard-block errors and recommends optional fields as warnings.
Other skills invoke this one via natural language — e.g., "Invoke the validating-fields skill to validate the task fields for target status Ready". When the agent receives that instruction, it loads this SKILL.md and follows the steps below.
Obtain the following from the invoking context:
Ready, In Progress, Blocked, Done, or CancelledConstruct a canonical flat JSON from the task data using the Construction Guide below. This normalizes provider differences (Notion rich_text arrays vs. SQLite strings) into a single shape the validator understands.
Write the canonical JSON to a writable temp file. /tmp/validate_task.json is the default; callers on read-only filesystems should pass a writable path instead (e.g., under ${TMPDIR}).
Run the validation script:
bash ${CLAUDE_SKILL_DIR}/scripts/validate-task-fields.sh <target_status> /tmp/validate_task.json
Parse the JSON output. It always has this shape:
{ "valid": true|false, "target_status": "<X>", "errors": [...], "warnings": [...] }
Return the result to the invoking skill's context. The invoking skill decides how to proceed:
valid: false → do NOT transition status; surface the errors to the user or abortvalid: true with warnings → transition is allowed; surface the warnings as advisoryvalid), but if it does, treat as a fatal environment error and report it to the caller.jq not installed: The script prints "Error: jq is required" to stderr and exits 1. Treat as an environment setup problem and surface it to the user.valid: false with an input field error. Treat as a caller bug — re-check the construction step./tmp not writable: Pass a writable path instead. The caller owns the temp file location.This skill is user-invocable: false — users do not trigger it directly via slash command. It is invoked by other skills (via natural language) and by developers when running the script manually during testing.
The validation script is provider-agnostic. Before calling it, construct a flat JSON object from whatever provider-specific format the task data is in:
{
"description": "Full task description text",
"acceptanceCriteria": "Verifiable completion conditions",
"executionPlan": "Step-by-step plan",
"issuer": true,
"assigneeCount": 1,
"priority": "High",
"executor": "cli",
"workingDirectory": "/absolute/path",
"branch": "feature-x",
"agentOutput": "Execution result",
"errorMessage": "Error details",
"createdAt": "2026-04-15T10:00:00.000Z",
"repository": "https://github.com/org/repo"
}
createdAt is used for legacy grandfathering of the Agent Output rule on Done transitions. repository is optional and enables repository-aware warnings when code tasks reach Ready without a working directory.
From Notion page object:
description <- .properties.Description.rich_text | map(.plain_text) | join("")
acceptanceCriteria <- .properties["Acceptance Criteria"].rich_text | map(.plain_text) | join("")
executionPlan <- .properties["Execution Plan"].rich_text | map(.plain_text) | join("")
issuer <- (.properties.Issuer.people | length) > 0
assigneeCount <- .properties.Assignee.people | length
priority <- .properties.Priority.select.name
executor <- .properties.Executor.select.name
workingDirectory <- .properties["Working Directory"].rich_text | map(.plain_text) | join("")
branch <- .properties.Branch.rich_text | map(.plain_text) | join("")
agentOutput <- .properties["Agent Output"].rich_text | map(.plain_text) | join("")
errorMessage <- .properties["Error Message"].rich_text | map(.plain_text) | join("")
createdAt <- .created_time
repository <- .properties.Repository.url // ""
From SQLite/Turso row:
description <- .description
acceptanceCriteria <- .acceptance_criteria
executionPlan <- .execution_plan
issuer <- (.issuer | length) > 0
assigneeCount <- (.assignee | fromjson | length)
priority <- .priority
executor <- .executor
workingDirectory <- .working_directory
branch <- .branch
agentOutput <- .agent_output
errorMessage <- .error_message
createdAt <- .created_at
repository <- .repository
{
"valid": true,
"target_status": "Ready",
"errors": [],
"warnings": [
{"field": "Issuer", "rule": "recommended", "message": "Issuer is empty. Consider setting it manually."}
]
}
valid: false -> block the transition, present errors to uservalid: true with warnings -> allow proceeding, present warnings to user.valid in the JSON output| Target Status | Required (errors) | Recommended (warnings) |
|---|---|---|
| Ready | Description (non-empty, >=50 chars), AC (non-empty + semantic check), Execution Plan (non-empty) | Issuer (non-empty), Assignee (non-empty), Priority (set), Working Directory & Repository (for AI code tasks — detected via keyword match) |
| In Progress | All Ready requirements + Executor (set), Working Directory (non-empty for AI executors) | Issuer, Branch (for cli executor) |
| Blocked | Description (non-empty), AC (non-empty) | Issuer, Error Message |
| Done | Description (non-empty), Agent Output (non-empty for AI executors on new tasks) | Agent Output (legacy tasks — created before the enforcement date — keep warning-only) |
| Cancelled | Description (non-empty) | — |
Issuer is always a warning, never an error -- ensures backward compatibility with pre-migration tasks.
Agent Output is required for AI executor tasks (cli / claude-code / claude-desktop / cowork) transitioning to Done. The requirement is enforced via a createdAt cutoff:
createdAt on or after the cutoff date: empty Agent Output → hard error (blocks the transition)createdAt before the cutoff date: empty Agent Output → warning only (does not block)This prevents retroactive invalidation of historical Done tasks while still enforcing the rule going forward. Human-executor tasks are never required to have Agent Output.
The cutoff date is hardcoded in scripts/validate-task-fields.sh as $agent_output_required_from. Update it only when introducing a similar migration — otherwise keep it stable.
AC text is scanned for at least one verifiable condition indicator:
npm, curl, git, python, bash, test, run, build, deploy/ or common extensions (.ts, .js, .py, .md, .html, .css)%, ms, s, count, times, itemsreturns, displays, creates, exists, passes, fails, contains, shows, generates, sends, receives, confirms, records, updatesIf none found -> error: "AC lacks verifiable conditions. Include commands, file paths, metrics, or observable outcomes."
This is a heuristic backstop, not a perfect quality gate. It catches worst-case garbage but not subtle gaps.
For AI-executor tasks transitioning to Ready, the script emits two warnings if the task looks like code work but has no Working Directory / Repository set:
config/code-task-keywords.txt (one keyword per line, # for comments)These remain warnings (not errors) at Ready; Working Directory becomes a hard error on In Progress. The warning gives the user an earlier nudge.
To adjust what counts as "code work", edit config/code-task-keywords.txt without touching the script.
These checks apply when parentTask is being set or a subtask is being created. They are separate from status-transition validation and must be checked by the caller (managing-tasks) before writing to the data source.
Before setting parentTask on task X to task Y, the caller MUST fetch task Y and verify that Y's own parentTask is null. If Y is already a subtask, reject with:
"Cannot create a 3rd-level subtask. Task '{Y.title}' is already a subtask of another task."
Before setting parentTask on task X to task Y, the caller MUST query whether any tasks have parentTask = X (i.e., X already has children). If X has children, reject with:
"Task '{X.title}' already has subtasks. A task with subtasks cannot itself become a subtask (2-level limit)."
This check is also enforced as a defense-in-depth in the validation script via the hasChildren field.
A task cannot reference itself as its own parent. If parentTask = X.id on task X, reject with:
"A task cannot be its own parent."
The validation script accepts an optional hasChildren boolean in the canonical input JSON. If parentTaskId is non-null and hasChildren is true, the script emits an error. This catches the case where a caller bypasses the managing-tasks pre-check.