From 962293016f42fc40c4c52aef66207e186b5be695 Mon Sep 17 00:00:00 2001 From: patel-lyzr Date: Thu, 5 Mar 2026 14:56:00 +0530 Subject: [PATCH] feat: add gitcron as native built-in tool and gitcron skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New built-in tool: gitcron — agents can schedule jobs, manage tasks, set reminders directly through the tool interface - Skills: scheduling, task-management, reminders — teach agents when and how to use gitcron capabilities - Registered in agent.yaml and createBuiltinTools() Co-Authored-By: Claude Opus 4.6 --- agent.yaml | 5 + skills/reminders/SKILL.md | 80 ++++++++++++++ skills/scheduling/SKILL.md | 96 +++++++++++++++++ skills/task-management/SKILL.md | 74 +++++++++++++ src/tools/gitcron.ts | 186 ++++++++++++++++++++++++++++++++ src/tools/index.ts | 6 +- 6 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 skills/reminders/SKILL.md create mode 100644 skills/scheduling/SKILL.md create mode 100644 skills/task-management/SKILL.md create mode 100644 src/tools/gitcron.ts diff --git a/agent.yaml b/agent.yaml index 03d81d9..9808605 100644 --- a/agent.yaml +++ b/agent.yaml @@ -10,5 +10,10 @@ tools: - read - write - memory + - gitcron +skills: + - scheduling + - task-management + - reminders runtime: max_turns: 50 diff --git a/skills/reminders/SKILL.md b/skills/reminders/SKILL.md new file mode 100644 index 0000000..79cb37d --- /dev/null +++ b/skills/reminders/SKILL.md @@ -0,0 +1,80 @@ +--- +name: reminders +description: Set recurring or one-shot reminders that create GitHub issues on schedule — for compliance deadlines, reviews, follow-ups, and check-ins +license: MIT +allowed-tools: gitcron cli read write +metadata: + author: open-gitagent + version: "1.0.0" + category: automation +--- + +# Reminders + +## Instructions + +You can create reminders that automatically create GitHub issues on a schedule or at a specific time. Reminders are defined in `cron.yaml` and compiled to GitHub Actions workflows. + +### Reminder types + +**Recurring** — Fires repeatedly on a cron schedule: +```yaml +- name: weekly-standup + type: recurring + cron: "0 9 * * 1" + action: + type: issue + title: "Weekly Standup Notes" + body: "Time for weekly standup. Update your status." + labels: [meeting, reminder] + assignees: [team-lead] +``` + +**One-shot** — Fires once at a specific time: +```yaml +- name: deadline-reminder + type: one-shot + at: "2026-04-01T09:00:00Z" + action: + type: issue + title: "Q1 Deadline: Final Review" +``` + +### When to create reminders + +- Compliance deadlines (quarterly reviews, annual audits) +- Follow-ups after deployments or incidents +- Recurring meetings or check-ins +- One-time deadlines or milestones +- Periodic health checks or reviews + +### Common cron patterns for reminders + +- `0 9 * * 1` — Every Monday at 9 AM (weekly standup) +- `0 9 1 * *` — First of month at 9 AM (monthly review) +- `0 9 1 */3 *` — Quarterly (every 3 months on the 1st) +- `0 9 1 1 *` — Annual (January 1st) +- `0 9 * * 1-5` — Every weekday at 9 AM + +### Managing reminders + +1. `remind-create` — Add a new recurring reminder +2. `remind-list` — See all reminders +3. `remind-fire` — Manually trigger a reminder now +4. `remind-pause` / `remind-resume` — Temporarily disable/enable +5. After changes, run `gitcron generate` to update workflows + +### Best practices + +- Use descriptive names in kebab-case: `quarterly-model-review`, not `qmr` +- Include actionable body text explaining what to do +- Add appropriate labels for filtering +- Assign to the responsible team/person +- For compliance reminders, reference the regulation (e.g., "per SR 11-7") + +## Output Format + +When creating a reminder, confirm: +1. Reminder name and schedule (human-readable) +2. What issue will be created (title, labels, assignees) +3. Next steps: `gitcron generate` and push diff --git a/skills/scheduling/SKILL.md b/skills/scheduling/SKILL.md new file mode 100644 index 0000000..d873d5e --- /dev/null +++ b/skills/scheduling/SKILL.md @@ -0,0 +1,96 @@ +--- +name: scheduling +description: Create and manage scheduled jobs that run as GitHub Actions — schedule agents, commands, code reviews, linting, and any recurring automation +license: MIT +allowed-tools: gitcron cli read write +metadata: + author: open-gitagent + version: "1.0.0" + category: automation +--- + +# Scheduling + +## Instructions + +You can create and manage scheduled jobs that run automatically as GitHub Actions workflows. Each schedule is defined in `cron.yaml` and compiled to `.github/workflows/` files. + +### When to create a schedule + +Create a schedule when the user wants something to run: +- On a recurring basis (nightly, weekly, monthly) +- Automatically without human intervention +- As a GitHub Actions workflow + +### Schedule types + +**Agent schedules** — Run a gitagent/gitclaw AI agent: +```yaml +- name: nightly-review + cron: "0 2 * * *" + agent: code-reviewer + adapter: claude # or: openai, gitclaw, system-prompt + prompt: "Review all open PRs" + branch: + strategy: pr # creates a PR with the agent's changes +``` + +**Command schedules** — Run any shell command: +```yaml +- name: weekly-lint + cron: "0 6 * * 1" + command: "npm run lint -- --fix" + branch: + strategy: commit # commits directly to base branch +``` + +### Cron expression reference + +``` +┌───────────── minute (0-59) +│ ┌─────────── hour (0-23) +│ │ ┌───────── day of month (1-31) +│ │ │ ┌─────── month (1-12) +│ │ │ │ ┌───── day of week (0-7, Sun=0 or 7) +│ │ │ │ │ +* * * * * +``` + +Common patterns: +- `0 2 * * *` — Daily at 2 AM +- `0 9 * * 1` — Every Monday at 9 AM +- `0 0 1 * *` — First of every month at midnight +- `*/15 * * * *` — Every 15 minutes +- `0 9 1 */3 *` — Quarterly (first of every 3rd month) + +### Branch strategies + +| Strategy | Use when | +|----------|----------| +| `pr` | Agent makes code changes that need review | +| `create` | Push a branch without opening a PR | +| `commit` | Small, safe changes (lint fixes, formatting) | +| `none` | Read-only tasks (audits, reports, scans) | + +### Workflow + +1. Use `gitcron` tool with `schedule-list` to see existing schedules +2. Use `gitcron` tool with appropriate command to create/modify schedules +3. Edit `cron.yaml` directly for complex configurations +4. Run `gitcron generate` to compile to GitHub Actions workflows +5. The workflows will run automatically after push to the repository + +### Adapter selection + +- **claude** — Best for code changes, complex reasoning. Uses Claude Code via gitagent. +- **gitclaw** — Full agent runtime with tools, hooks, audit logging, compliance. Best for enterprise use. +- **openai** — OpenAI API integration via gitagent. +- **system-prompt** — Generic LLM export. + +## Output Format + +When creating a schedule, confirm: +1. Schedule name and cron expression (human-readable) +2. What it does (agent/command) +3. Branch strategy chosen and why +4. Next steps: `gitcron generate` and push diff --git a/skills/task-management/SKILL.md b/skills/task-management/SKILL.md new file mode 100644 index 0000000..51c6ee2 --- /dev/null +++ b/skills/task-management/SKILL.md @@ -0,0 +1,74 @@ +--- +name: task-management +description: Git-native task tracking with state machine — create, assign, transition, and track tasks where every mutation is a git commit +license: MIT +allowed-tools: gitcron cli read write +metadata: + author: open-gitagent + version: "1.0.0" + category: project-management +--- + +# Task Management + +## Instructions + +You can create and manage tasks tracked as YAML files in `.gitcron/tasks/`. Every task mutation (create, state change, reassign) creates a git commit, giving you a full audit trail. + +### When to use tasks + +- Track work items discovered during agent execution +- Break down complex requests into trackable units +- Assign work to team members or other agents +- Track progress through defined states + +### Task states + +Default state machine: +``` +pending → in_progress → review → done + ↓ ↓ ↓ +cancelled cancelled (back to in_progress) +``` + +Valid transitions: +- `pending` → `in_progress`, `cancelled` +- `in_progress` → `review`, `done`, `cancelled` +- `review` → `in_progress`, `done` +- `done` → (terminal) +- `cancelled` → (terminal) + +### Creating tasks + +Use the `gitcron` tool with `task-create`: +- Always provide a clear, actionable title +- Set priority based on urgency: `high`, `medium`, `low` +- Assign if the responsible person/agent is known + +### Updating tasks + +Use `task-update` with the task ID (e.g., TASK-001): +- Only transition to valid next states +- The tool will reject invalid transitions + +### Workflow + +1. `task-list` to see current tasks and their states +2. `task-create` for new work items +3. `task-update` to progress tasks through states +4. `task-show` for full details including history + +### Best practices + +- One task per discrete work item +- Use descriptive titles that explain the outcome, not the process +- Set priority based on impact: `high` = blocking/urgent, `medium` = normal, `low` = nice-to-have +- Move tasks to `review` when they need human verification +- Move to `done` only when fully complete + +## Output Format + +When managing tasks, report: +1. Action taken (created/updated/listed) +2. Task ID and current state +3. What changed and why diff --git a/src/tools/gitcron.ts b/src/tools/gitcron.ts new file mode 100644 index 0000000..5a1cf01 --- /dev/null +++ b/src/tools/gitcron.ts @@ -0,0 +1,186 @@ +import { spawn } from "child_process"; +import { access } from "fs/promises"; +import { join } from "path"; +import { Type } from "@sinclair/typebox"; +import { StringEnum } from "@mariozechner/pi-ai"; +import type { AgentTool } from "@mariozechner/pi-agent-core"; +import { MAX_OUTPUT } from "./shared.js"; + +export const gitcronSchema = Type.Object({ + command: StringEnum( + ["schedule-create", "schedule-list", "task-create", "task-list", "task-update", "task-show", "remind-create", "remind-list", "remind-fire", "remind-pause", "remind-resume", "status", "validate", "generate"], + { description: "The gitcron command to run" }, + ), + name: Type.Optional(Type.String({ description: "Schedule/reminder name in kebab-case" })), + cron: Type.Optional(Type.String({ description: "Cron expression (e.g., '0 2 * * *')" })), + title: Type.Optional(Type.String({ description: "Task title or issue title" })), + id: Type.Optional(Type.String({ description: "Task ID (e.g., TASK-001)" })), + state: Type.Optional(Type.String({ description: "Task state (pending, in_progress, review, done, cancelled)" })), + priority: Type.Optional(StringEnum(["low", "medium", "high"], { description: "Task priority" })), + assignee: Type.Optional(Type.String({ description: "Assignee name" })), + agent: Type.Optional(Type.String({ description: "Agent name for schedule" })), + adapter: Type.Optional(StringEnum(["claude", "openai", "gitclaw", "system-prompt"], { description: "Adapter for agent execution" })), + prompt: Type.Optional(Type.String({ description: "Prompt for agent schedule" })), + shell_command: Type.Optional(Type.String({ description: "Shell command for command-type schedule" })), + strategy: Type.Optional(StringEnum(["pr", "create", "commit", "none"], { description: "Branch strategy" })), + body: Type.Optional(Type.String({ description: "Issue body for reminders" })), + dry_run: Type.Optional(Type.Boolean({ description: "Preview without writing (for generate)" })), +}); + +interface GitcronArgs { + command: string; + name?: string; + cron?: string; + title?: string; + id?: string; + state?: string; + priority?: string; + assignee?: string; + agent?: string; + adapter?: string; + prompt?: string; + shell_command?: string; + strategy?: string; + body?: string; + dry_run?: boolean; +} + +function buildCliArgs(args: GitcronArgs): string[] { + switch (args.command) { + case "schedule-list": + return ["list", "--schedules"]; + + case "schedule-create": + // Schedule creation is done by editing cron.yaml — we use the generate after + return ["list", "--schedules"]; + + case "task-create": { + const cmd = ["task", "create", args.title || "Untitled"]; + if (args.priority) cmd.push("--priority", args.priority); + if (args.assignee) cmd.push("--assignee", args.assignee); + return cmd; + } + + case "task-list": { + const cmd = ["task", "list"]; + if (args.state) cmd.push("--state", args.state); + return cmd; + } + + case "task-update": { + const cmd = ["task", "update", args.id || ""]; + if (args.state) cmd.push("--state", args.state); + if (args.assignee) cmd.push("--assignee", args.assignee); + return cmd; + } + + case "task-show": + return ["task", "show", args.id || ""]; + + case "remind-create": { + const cmd = ["remind", "create", args.name || ""]; + if (args.cron) cmd.push("--cron", args.cron); + if (args.title) cmd.push("--title", args.title); + if (args.body) cmd.push("--body", args.body); + return cmd; + } + + case "remind-list": + return ["remind", "list"]; + + case "remind-fire": + return ["remind", "fire", args.name || ""]; + + case "remind-pause": + return ["remind", "pause", args.name || ""]; + + case "remind-resume": + return ["remind", "resume", args.name || ""]; + + case "status": + return ["status"]; + + case "validate": + return ["validate"]; + + case "generate": { + const cmd = ["generate"]; + if (args.dry_run) cmd.push("--dry-run"); + return cmd; + } + + default: + return ["--help"]; + } +} + +export function createGitcronTool(cwd: string): AgentTool { + return { + name: "gitcron", + label: "gitcron", + description: + "Git-native scheduling, tasks, and reminders. Create scheduled jobs (cron → GitHub Actions), manage tasks with state tracking, and set reminders. Commands: schedule-create/list, task-create/list/update/show, remind-create/list/fire/pause/resume, status, validate, generate.", + parameters: gitcronSchema, + execute: async ( + _toolCallId: string, + args: GitcronArgs, + signal?: AbortSignal, + ) => { + if (signal?.aborted) throw new Error("Operation aborted"); + + // Check if gitcron is available + const cliArgs = buildCliArgs(args); + + return new Promise((resolve, reject) => { + const child = spawn("gitcron", cliArgs, { + cwd, + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env }, + }); + + let output = ""; + + child.stdout.on("data", (data: Buffer) => { + output += data.toString("utf-8"); + }); + child.stderr.on("data", (data: Buffer) => { + output += data.toString("utf-8"); + }); + + const timeout = setTimeout(() => { + child.kill("SIGTERM"); + reject(new Error("gitcron command timed out after 30s")); + }, 30_000); + + const onAbort = () => child.kill("SIGTERM"); + if (signal) signal.addEventListener("abort", onAbort, { once: true }); + + child.on("error", (err) => { + clearTimeout(timeout); + if (signal) signal.removeEventListener("abort", onAbort); + reject(new Error(`gitcron not found. Install with: npm install -g gitcron. Error: ${err.message}`)); + }); + + child.on("close", (code) => { + clearTimeout(timeout); + if (signal) signal.removeEventListener("abort", onAbort); + + if (signal?.aborted) { + reject(new Error("Operation aborted")); + return; + } + + const trimmed = output.trim(); + const text = trimmed.length > MAX_OUTPUT + ? trimmed.slice(-MAX_OUTPUT) + : trimmed || "(no output)"; + + resolve({ + content: [{ type: "text", text }], + details: { exitCode: code }, + }); + }); + }); + }, + }; +} diff --git a/src/tools/index.ts b/src/tools/index.ts index 8e2b2c8..4f84677 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -4,6 +4,7 @@ import { createCliTool } from "./cli.js"; import { createReadTool } from "./read.js"; import { createWriteTool } from "./write.js"; import { createMemoryTool } from "./memory.js"; +import { createGitcronTool } from "./gitcron.js"; import { createSandboxCliTool } from "./sandbox-cli.js"; import { createSandboxReadTool } from "./sandbox-read.js"; import { createSandboxWriteTool } from "./sandbox-write.js"; @@ -16,9 +17,11 @@ export interface BuiltinToolsConfig { } /** - * Create the four built-in tools (cli, read, write, memory). + * Create the built-in tools (cli, read, write, memory, gitcron). * If a SandboxContext is provided, returns sandbox-backed tools; * otherwise returns the standard local tools. + * The gitcron tool is always included — it gracefully fails if + * gitcron CLI is not installed. */ export function createBuiltinTools(config: BuiltinToolsConfig): AgentTool[] { if (config.sandbox) { @@ -35,5 +38,6 @@ export function createBuiltinTools(config: BuiltinToolsConfig): AgentTool[] createReadTool(config.dir), createWriteTool(config.dir), createMemoryTool(config.dir), + createGitcronTool(config.dir), ]; }