Skip to content

feat(workflows): Quick Workflows — @slug omnibar, management UI, cron scheduling#106

Merged
tstapler merged 4 commits into
mainfrom
stapler-squad-quick-workflows
Jun 12, 2026
Merged

feat(workflows): Quick Workflows — @slug omnibar, management UI, cron scheduling#106
tstapler merged 4 commits into
mainfrom
stapler-squad-quick-workflows

Conversation

@tstapler

Copy link
Copy Markdown
Owner

Summary

  • New feature: Define named workflow templates (skill/command, target directory, input template, session type, model) and invoke them from the omnibar with @slug [optional-arg], from a dedicated management panel, or on a cron schedule
  • Storage: Workflow ent entity in SQLite — auto-migrated on startup, no manual SQL needed
  • Omnibar: WorkflowDetector at priority 25 uses @slug [arg] syntax — verified collision-free against all 9 existing detectors; run_workflow OmnibarAction dispatches via RunWorkflow RPC
  • Cron: WorkflowScheduler wraps robfig/cron/v3, hot-reloads on CRUD changes, fires one-off sessions; circular import avoided via WorkflowSchedulerInterface + SessionServiceInterface + deferred SetWorkflowService injection
  • 5 new RPCs on SessionService: CreateWorkflow, UpdateWorkflow, DeleteWorkflow, ListWorkflows, RunWorkflow

Usage

# Omnibar — type @ to invoke a workflow
@knowledge-sync https://example.com/article

# Nav → Workflows — create/edit/delete saved workflows
/workflows

Architecture notes

  • No new session creation type — workflows reuse directory / one_off existing paths (7-touchpoint registry not required)
  • targetDirectory validated as required at RPC handler layer; Optional in ent schema for future flexibility
  • Missed-fire policy: cron runs skipped during downtime are not backfilled (v1 non-goal, documented)

Test plan

  • Go unit + integration tests: 730 service tests pass (including 8 new workflow service tests), 1113 session/workflows tests pass
  • Frontend: 42 Jest tests pass (18 WorkflowDetector + 3 dispatch + existing)
  • TypeScript: zero new errors (npx tsc --noEmit)
  • Slug validation: 15 unit tests in workflow_slug_test.go
  • E2E Playwright: tests/e2e/workflows.spec.ts requires live server — run with make install-service + cd tests/e2e && npm test
  • Cron: manually verify by creating a workflow with a near-future cron expression and checking session creation in the UI

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 12, 2026 00:25
@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 102 feature files to /tmp/tmp.FXyZDl8tHe/backend
Wrote 14 feature files to /tmp/tmp.FXyZDl8tHe/backend
Wrote 22 feature files to /tmp/tmp.FXyZDl8tHe/backend
Wrote 6 feature files to /tmp/tmp.FXyZDl8tHe/backend

=== Backend Registry Diff ===
Committed: 136  Generated: 135  Divergence: 0.74%
⚠️  Removed RPCs:
  - upload:image
⚠️  95 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.74%

Test Coverage: 97/136 features have testIds (71.3%)

Registry validation is in observation mode until 2026-05-02.
After that date, divergence > 2% will block merges.
Coverage reporting is advisory only.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Go Benchmarks (Tier 1)

benchmarks/go/tier1-baseline.txt:8: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:2035: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:4040: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:6040: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:8071: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:10099: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:12134: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:14168: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:16192: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:22884: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:30608: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:37827: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:44589: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:51346: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:58304: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:64780: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:71600: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:77066: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:82029: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:87665: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:93279: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:98752: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:104017: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:109727: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:115757: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:123122: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:130424: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:137211: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:144671: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:152393: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:159266: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:166225: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:173299: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:180826: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:188270: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:196607: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:205005: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:212668: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:220592: parsing iteration count: invalid syntax
benchmarks/go/tier1-baseline.txt:228363: parsing iteration count: invalid syntax
tier1-bench.txt:8: parsing iteration count: invalid syntax
tier1-bench.txt:4112: parsing iteration count: invalid syntax
tier1-bench.txt:6031: parsing iteration count: invalid syntax
tier1-bench.txt:7958: parsing iteration count: invalid syntax
tier1-bench.txt:9899: parsing iteration count: invalid syntax
tier1-bench.txt:11823: parsing iteration count: invalid syntax
tier1-bench.txt:13810: parsing iteration count: invalid syntax
tier1-bench.txt:15820: parsing iteration count: invalid syntax
tier1-bench.txt:17819: parsing iteration count: invalid syntax
tier1-bench.txt:24368: parsing iteration count: invalid syntax
tier1-bench.txt:31237: parsing iteration count: invalid syntax
tier1-bench.txt:37992: parsing iteration count: invalid syntax
tier1-bench.txt:44403: parsing iteration count: invalid syntax
tier1-bench.txt:50445: parsing iteration count: invalid syntax
tier1-bench.txt:57667: parsing iteration count: invalid syntax
tier1-bench.txt:64547: parsing iteration count: invalid syntax
tier1-bench.txt:71015: parsing iteration count: invalid syntax
tier1-bench.txt:76585: parsing iteration count: invalid syntax
tier1-bench.txt:82560: parsing iteration count: invalid syntax
tier1-bench.txt:88256: parsing iteration count: invalid syntax
tier1-bench.txt:93670: parsing iteration count: invalid syntax
tier1-bench.txt:98808: parsing iteration count: invalid syntax
tier1-bench.txt:104354: parsing iteration count: invalid syntax
tier1-bench.txt:109560: parsing iteration count: invalid syntax
tier1-bench.txt:114573: parsing iteration count: invalid syntax
tier1-bench.txt:121403: parsing iteration count: invalid syntax
tier1-bench.txt:128581: parsing iteration count: invalid syntax
tier1-bench.txt:135477: parsing iteration count: invalid syntax
tier1-bench.txt:142515: parsing iteration count: invalid syntax
tier1-bench.txt:148930: parsing iteration count: invalid syntax
tier1-bench.txt:155823: parsing iteration count: invalid syntax
tier1-bench.txt:162879: parsing iteration count: invalid syntax
tier1-bench.txt:169660: parsing iteration count: invalid syntax
tier1-bench.txt:176957: parsing iteration count: invalid syntax
tier1-bench.txt:184406: parsing iteration count: invalid syntax
tier1-bench.txt:191680: parsing iteration count: invalid syntax
tier1-bench.txt:199545: parsing iteration count: invalid syntax
tier1-bench.txt:206997: parsing iteration count: invalid syntax
tier1-bench.txt:213818: parsing iteration count: invalid syntax
tier1-bench.txt:221343: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/tstapler/stapler-squad/session/detection/ratelimit
cpu: AMD EPYC 9V74 80-Core Processor                
                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                              │              sec/op              │   sec/op     vs base              │
StripANSI_PlainText-4                                6.915n ± 4%   7.006n ± 3%       ~ (p=0.161 n=8)
StripANSI_WithEscapes-4                              665.6n ± 1%   665.5n ± 0%       ~ (p=0.625 n=8)
ProcessOutput_InactiveState-4                        6.606n ± 0%   6.612n ± 0%       ~ (p=0.153 n=8)
geomean                                              31.21n        31.36n       +0.47%

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/op               │    B/op     vs base                │
StripANSI_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSI_WithEscapes-4                             136.0 ± 0%     136.0 ± 0%       ~ (p=1.000 n=8) ¹
ProcessOutput_InactiveState-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │            allocs/op             │ allocs/op   vs base                │
StripANSI_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSI_WithEscapes-4                             5.000 ± 0%     5.000 ± 0%       ~ (p=1.000 n=8) ¹
ProcessOutput_InactiveState-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/queue
                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │              sec/op              │    sec/op     vs base              │
ReviewQueue_ConcurrentReads-4                       91.80n ± 13%   91.21n ± 27%       ~ (p=0.185 n=8)
ReviewQueue_Add-4                                   406.1n ±  0%   406.9n ±  1%       ~ (p=0.223 n=8)
geomean                                             193.1n         192.6n        -0.23%

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │               B/op               │    B/op     vs base                │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
ReviewQueue_Add-4                                   640.0 ± 0%     640.0 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                              │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                              │            allocs/op             │ allocs/op   vs base                │
ReviewQueue_ConcurrentReads-4                       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
ReviewQueue_Add-4                                   4.000 ± 0%     4.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                        ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/scrollback
                                      │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                      │              sec/op              │   sec/op     vs base              │
CircularBuffer_ConcurrentReadWrite-4                         3.167µ ± 2%   3.117µ ± 1%  -1.56% (p=0.010 n=8)
CircularBuffer_BurstAppend-4                                 105.3µ ± 1%   105.3µ ± 0%       ~ (p=0.959 n=8)
CircularBuffer_GetLastN_LargeBuffer-4                        15.68µ ± 1%   15.83µ ± 3%       ~ (p=0.328 n=8)
CircularBuffer_GetRange_Sequential-4                         9.069µ ± 3%   9.082µ ± 2%       ~ (p=1.000 n=8)
CircularBufferAppend-4                                       103.4n ± 0%   102.2n ± 0%  -1.06% (p=0.000 n=8)
CircularBufferGetLastN-4                                     1.752µ ± 1%   1.763µ ± 1%       ~ (p=0.168 n=8)
CircularBufferConcurrentAppend-4                             133.9n ± 0%   133.9n ± 0%       ~ (p=0.821 n=8)
geomean                                                      2.737µ        2.733µ       -0.12%

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                      │               B/op               │     B/op      vs base                │
CircularBuffer_ConcurrentReadWrite-4                        6.062Ki ± 0%   6.062Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                62.50Ki ± 0%   62.50Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                       56.00Ki ± 0%   56.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                        28.00Ki ± 0%   28.00Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        24.00 ± 0%     24.00 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                    6.000Ki ± 0%   6.000Ki ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              32.00 ± 0%     32.00 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                     3.077Ki        3.077Ki       +0.00%
¹ all samples are equal

                                      │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt           │
                                      │            allocs/op             │  allocs/op   vs base                │
CircularBuffer_ConcurrentReadWrite-4                          2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_BurstAppend-4                                 1.000k ± 0%   1.000k ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetLastN_LargeBuffer-4                         1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBuffer_GetRange_Sequential-4                          1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferAppend-4                                        1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferGetLastN-4                                      1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
CircularBufferConcurrentAppend-4                              1.000 ± 0%    1.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       2.962         2.962       +0.00%
¹ all samples are equal

                             │ benchmarks/go/tier1-baseline.txt │        tier1-bench.txt        │
                             │               B/s                │     B/s       vs base         │
CircularBuffer_BurstAppend-4                       579.5Mi ± 1%   579.5Mi ± 0%  ~ (p=0.959 n=8)

pkg: github.com/tstapler/stapler-squad/session/tmux
                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                             │              sec/op              │   sec/op     vs base              │
StripANSICodes_PlainText-4                          6.649n ± 5%   6.454n ± 4%       ~ (p=0.959 n=8)
StripANSICodes_WithEscapes-4                        599.5n ± 1%   598.8n ± 1%       ~ (p=0.504 n=8)
IsBanner_PlainText-4                                452.4n ± 1%   453.9n ± 1%       ~ (p=0.520 n=8)
geomean                                             121.7n        120.6n       -0.91%

                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                             │               B/op               │    B/op     vs base                │
StripANSICodes_PlainText-4                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSICodes_WithEscapes-4                       56.00 ± 0%     56.00 ± 0%       ~ (p=1.000 n=8) ¹
IsBanner_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                             │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                             │            allocs/op             │ allocs/op   vs base                │
StripANSICodes_PlainText-4                         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
StripANSICodes_WithEscapes-4                       4.000 ± 0%     4.000 ± 0%       ~ (p=1.000 n=8) ¹
IsBanner_PlainText-4                               0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                       ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/tstapler/stapler-squad/session/tokens
                                   │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt          │
                                   │              sec/op              │   sec/op     vs base              │
TokenParser_ProcessUserEntry-4                            5.469m ± 1%   5.502m ± 1%  +0.60% (p=0.038 n=8)
DetectCommandsInText/NoSlash-4                            6.697n ± 2%   6.689n ± 3%       ~ (p=0.159 n=8)
DetectCommandsInText/WithCommand-4                        1.443µ ± 1%   1.441µ ± 0%       ~ (p=0.556 n=8)
geomean                                                   3.752µ        3.757µ       +0.11%

                                   │ benchmarks/go/tier1-baseline.txt │           tier1-bench.txt            │
                                   │               B/op               │     B/op      vs base                │
TokenParser_ProcessUserEntry-4                         11.02Mi ± 0%     11.02Mi ± 0%       ~ (p=0.479 n=8)
DetectCommandsInText/NoSlash-4                           0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/WithCommand-4                       433.5 ± 0%       433.0 ± 0%       ~ (p=1.000 n=8)
geomean                                                             ²                 -0.04%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                   │ benchmarks/go/tier1-baseline.txt │          tier1-bench.txt           │
                                   │            allocs/op             │ allocs/op   vs base                │
TokenParser_ProcessUserEntry-4                           34.00 ± 0%     34.00 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/NoSlash-4                           0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=8) ¹
DetectCommandsInText/WithCommand-4                       6.000 ± 0%     6.000 ± 0%       ~ (p=1.000 n=8) ¹
geomean                                                             ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

E2E RPC Latency

list-sessions-ttfb-mean: 5ms (▼ faster -17.5%; baseline: 7ms)
list-sessions-total-mean: 7ms (▼ faster -12.6%; baseline: 8ms)

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Frontend Terminal Throughput

terminal-throughput-mean: 14 KB/s ▲ +2.3% (baseline: 14 KB/s)
terminal-throughput-p50: 16 KB/s ▲ +1.3% (baseline: 16 KB/s)

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

UX Analysis

Check Status Details
✅ Axe Core (WCAG 2.1 AA) success Critical/serious violations block merge
⚠️ Lighthouse Performance Score: unknown Warning if < 70 (non-blocking)
🤖 Claude UX Analysis Advisory See docs/qa/ for findings

Axe Core excludes terminal rendering areas (intentional design).
Lighthouse runs in desktop preset for this developer tool.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🎬 E2E Feature Demos

2 shard(s) recorded feature flows for this PR.

recordings shard 1
recordings shard 2

Demo preview opens directly in browser (single-file HTML). Raw WebM recordings in ZIP. Expires after 30 days.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a “Quick Workflows” feature across the Stapler Squad stack: workflows are persisted in SQLite (ent), exposed via new SessionService RPCs, invokable via @slug in the omnibar, manageable via a new /workflows UI, and optionally schedulable via cron (robfig/cron).

Changes:

  • Adds workflow persistence (ent schema + repository) and 5 new workflow RPCs on SessionService (CRUD + RunWorkflow).
  • Implements a cron-backed WorkflowScheduler plus server wiring to start/stop it and hot-reload entries on workflow changes.
  • Adds frontend management UI (/workflows) and omnibar detection/dispatch plumbing for @slug [arg].

Reviewed changes

Copilot reviewed 58 out of 63 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
web-app/src/lib/routes.ts Adds /workflows route constant.
web-app/src/lib/omnibar/types.ts Adds InputType.Workflow metadata for omnibar.
web-app/src/lib/omnibar/detectors/WorkflowDetector.ts New detector for @slug [arg] parsing.
web-app/src/lib/omnibar/detectors/WorkflowDetector.test.ts Unit tests for WorkflowDetector behavior.
web-app/src/lib/omnibar/detector.ts Adds registry unregister() + resetDefaultRegistry() for tests.
web-app/src/lib/omnibar/actions/types.ts Extends OmnibarAction union with run_workflow.
web-app/src/lib/omnibar/actions/dispatch.ts Dispatch support + analytics for run_workflow.
web-app/src/lib/omnibar/actions/dispatch.test.ts Tests for run_workflow dispatch behavior.
web-app/src/lib/nav-pages.ts Adds “Workflows” nav entry.
web-app/src/lib/hooks/useWorkflows.ts New ConnectRPC hook to CRUD workflows.
web-app/src/lib/hooks/useSessionService.ts Adds runWorkflow RPC helper to the session service hook.
web-app/src/lib/contexts/OmnibarContext.tsx Dynamically registers WorkflowDetector + wires omnibar run handler.
web-app/src/components/workflows/WorkflowsPanel.tsx New workflows management panel (CRUD UI).
web-app/src/components/workflows/WorkflowsPanel.css.ts Styles for workflows panel and modal overlay.
web-app/src/components/workflows/WorkflowForm.tsx Create/edit workflow form UI.
web-app/src/components/workflows/WorkflowForm.css.ts Styles for the workflow form.
web-app/src/components/sessions/Omnibar.tsx Adds onRunWorkflow handling for workflow detections.
web-app/src/app/workflows/page.tsx New /workflows page entrypoint.
web-app/src/app/workflows/page.css.ts Page layout styling for /workflows.
web-app/src/app/workflows/layout.tsx Next.js metadata/layout wrapper for /workflows.
session/workflow_slug.go Adds backend slug validation helper.
session/workflow_slug_test.go Tests for workflow slug validation.
session/workflow_repository.go Defines workflow repository interface + input structs.
session/session_driver.go Removes trailing whitespace (no functional change).
session/instance_claude.go Removes trailing whitespace (no functional change).
session/ent/schema/workflow.go Adds ent Workflow schema.
session/ent/schema/approvalrule.go Makes several JSON fields nullable to ease SQLite migration.
session/ent/runtime.go Registers generated runtime hooks/validators for Workflow.
session/ent/migrate/schema.go Adds workflows table schema + nullable JSON columns.
session/ent/hook/hook.go Adds ent hook adapter type for Workflow mutations.
session/ent/approvalrule/where.go Adds IsNil/NotNil predicates for nullable JSON columns.
session/ent/approvalrule_update.go Adds Clear* helpers + SQL clear handling for nullable JSON fields.
session/ent/approvalrule_create.go Removes “missing required field” checks for now-optional JSON fields; adds Clear* upsert helpers.
session/ent_workflow_repository.go Implements workflow persistence via ent.
server/workflows/scheduler.go Implements cron scheduler + FireNow + validation helper.
server/services/workflow_service.go Implements workflow RPCs (CRUD + RunWorkflow).
server/services/workflow_service_test.go Adds CRUD-focused workflow service tests.
server/services/session_service.go Adds workflow subservice injection + delegates RPC methods.
server/server.go Starts workflow scheduler during server wiring.
server/dependencies.go Wires WorkflowRepo + WorkflowScheduler into dependency graph.
scripts/install-service.sh Adds health check + auto-rollback behavior after install.
proto/session/v1/session.proto Adds workflow RPCs + messages to SessionService proto.
project_plans/quick-workflows/research/ui-patterns.md Research notes for UI patterns/navigation.
project_plans/quick-workflows/research/stack-storage.md Research notes for workflow persistence options.
project_plans/quick-workflows/research/omnibar-architecture.md Research notes for omnibar detector/action architecture.
project_plans/quick-workflows/research/cron-scheduling.md Research notes for cron scheduler approach.
project_plans/quick-workflows/requirements.md Requirements document for the feature.
project_plans/quick-workflows/implementation/validation.md Validation/test planning document.
Makefile Adds backup/rollback targets and integrates install-service backup behavior.
go.sum Adds robfig/cron checksum entries.
go.mod Adds robfig/cron dependency.
gen/proto/go/session/v1/sessionv1connect/session.connect.go Generated Connect client/server glue for new workflow RPCs.
docs/registry/features/workflows-management.json Adds frontend registry entry for workflows UI.
docs/registry/features/workflow-detector.json Adds frontend registry entry for workflow detector.
docs/registry/features/backend/UpdateWorkflow.json Adds backend registry entry for UpdateWorkflow RPC.
docs/registry/features/backend/RunWorkflow.json Adds backend registry entry for RunWorkflow RPC.
docs/registry/features/backend/ListWorkflows.json Adds backend registry entry for ListWorkflows RPC.
docs/registry/features/backend/DeleteWorkflow.json Adds backend registry entry for DeleteWorkflow RPC.
docs/registry/features/backend/CreateWorkflow.json Adds backend registry entry for CreateWorkflow RPC.
Files not reviewed (4)
  • gen/proto/go/session/v1/session.pb.go: Generated file
  • gen/proto/go/session/v1/sessionv1connect/session.connect.go: Generated file
  • session/ent/approvalrule/where.go: Generated file
  • session/ent/approvalrule_create.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/workflows/scheduler.go Outdated
Comment on lines +132 to +137
prompt := wf.InputTemplate
if prompt != "" && arg != "" {
prompt = strings.ReplaceAll(prompt, "{{input}}", arg)
} else if prompt == "" {
prompt = arg
}
Comment on lines +169 to +174
wfCopy := wf // capture for closure
entryID, err := s.c.AddFunc(wf.CronExpression, func() {
ctx := context.Background()
if _, fireErr := s.FireNow(ctx, wfCopy, ""); fireErr != nil {
log.Error("[WorkflowScheduler] cron job failed", "slug", wfCopy.Slug, "err", fireErr)
}
Comment on lines +71 to +83
// Validate required fields per ADR-9.
if req.Msg.Command == "" {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("command is required"))
}
if req.Msg.TargetDirectory == "" {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("target_directory is required"))
}
// Validate cron expression if provided.
if req.Msg.CronExpression != "" {
if err := workflows.ValidateCronExpression(req.Msg.CronExpression); err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("invalid cron expression: %w", err))
}
}
Comment on lines +125 to +132
const deleteWorkflow = useCallback(
async (id: string) => {
if (!clientRef.current) return;
// Optimistic update.
setWorkflows((prev) => prev.filter((w) => w.id !== id));
const req = create(DeleteWorkflowRequestSchema, { id });
await clientRef.current.deleteWorkflow(req);
},
Comment on lines +90 to +94
required
disabled={isEdit}
pattern="[a-z0-9][a-z0-9-]*[a-z0-9]"
title="2-64 lowercase chars, hyphens allowed, no leading/trailing hyphens"
/>
Comment on lines +1 to +5
/**
* WorkflowDetector detects @slug [arg] syntax for quick workflow invocation.
* Priority 25 — after GitHub URL detectors (10/20/30) and before NewSessionDetector (35).
*/

Comment thread go.mod Outdated
Comment on lines 47 to 51
require (
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
)
Comment on lines +3478 to +3482
// CreateWorkflow delegates to WorkflowService.
func (s *SessionService) CreateWorkflow(ctx context.Context, req *connect.Request[sessionv1.CreateWorkflowRequest]) (*connect.Response[sessionv1.CreateWorkflowResponse], error) {
if s.workflowSvc == nil {
return nil, connect.NewError(connect.CodeUnavailable, fmt.Errorf("workflow service not available"))
}
Comment thread docs/registry/features/workflow-detector.json
Comment thread server/server.go
Comment on lines +353 to +358
// Start WorkflowScheduler (nil guard: disabled when workflow repo is unavailable).
if deps.WorkflowScheduler != nil {
deps.WorkflowScheduler.Start(serverCtx)
srv.shutdownHooks = append(srv.shutdownHooks, deps.WorkflowScheduler.Stop)
log.Info("WorkflowScheduler started")
}
@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 102 feature files to /tmp/tmp.QVjMtaMFZC/backend
Wrote 14 feature files to /tmp/tmp.QVjMtaMFZC/backend
Wrote 22 feature files to /tmp/tmp.QVjMtaMFZC/backend
Wrote 6 feature files to /tmp/tmp.QVjMtaMFZC/backend

=== Backend Registry Diff ===
Committed: 136  Generated: 135  Divergence: 0.74%
⚠️  Removed RPCs:
  - upload:image
⚠️  95 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.74%

Test Coverage: 97/136 features have testIds (71.3%)

Registry validation is in observation mode until 2026-05-02.
After that date, divergence > 2% will block merges.
Coverage reporting is advisory only.

tstapler and others added 4 commits June 11, 2026 18:50
…ent UI, cron scheduling

Introduces a flexible workflow definition system that lets users save named
configurations (skill/command, target directory, input template, session type,
model) and invoke them instantly from the omnibar, a dedicated management panel,
or on a cron schedule.

Key additions:
- `@slug [arg]` omnibar syntax (WorkflowDetector, priority 25, collision-free)
- `/workflows` management page: create/edit/delete workflows via WorkflowForm + WorkflowsPanel
- WorkflowScheduler (robfig/cron/v3) fires one-off sessions on schedule; hot-reloads on CRUD
- 5 new proto RPCs: CreateWorkflow, UpdateWorkflow, DeleteWorkflow, ListWorkflows, RunWorkflow
- ent ORM Workflow entity (auto-migrated on startup, slug UNIQUE index)
- Circular import avoided: WorkflowSchedulerInterface in services/, SessionServiceInterface in workflows/, deferred SetWorkflowService injection
- 64 new tests (Go unit/integration + Jest + slug validation)
- Missed-fire policy: skipped cron runs during downtime are not backfilled (v1 non-goal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BLOCKER/CRITICAL:
- Log Reload errors instead of discarding with _ (B1)
- Use errors.Is(err, session.ErrNotFound) instead of ent.IsNotFound after repo wrapping (B2)
- Add 8-second Stop() timeout to prevent hung server shutdown (C2)
- Rollback optimistic delete on RPC failure in useWorkflows (C3)
- Reject cron_enabled=true with empty cron_expression at validation layer (C4)
- Add ErrConflict sentinel; ent_workflow_repository converts ConstraintError (D3/D5)
- Validate target_directory is absolute and free of traversal components (S1/S2)

MAJOR:
- Add 5-minute timeout to cron callback context (M1)
- Add SessionTypeOneOff constant; scheduler uses it instead of magic string (M4)
- Replace window.confirm with inline confirmDeleteId state in WorkflowsPanel (M5)
- Add Limit(1000) safety cap to ListAll (A4)

Tests:
- mockScheduler struct; TestCreateWorkflow_WithCronEnabled_CallsReload (T1)
- TestRunWorkflow_HappyPath + TestRunWorkflow_NotFound (T2)
- Fix delegation test: create workflow first, assert Len==1 (T3)
- Add multi-field UpdateWorkflow test (T4)
- Add 64-char and 65-char slug boundary cases (T5)
- Add default registry @-input fallthrough test (T6)
- Add analytics.track assertion for run_workflow dispatch (T7)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s, registry format

- FireNow: include wf.Command in InitialPrompt (was silently dropped; sessions
  launched without the core skill/command entirely)
- CreateWorkflow: add Name == "" validation for explicit CodeInvalidArgument
- scheduler.Start: remove duplicate Stop() goroutine (server.go already registers
  Stop as a shutdown hook, creating a double-stop risk)
- session_service.go: add // +api: markers to all 5 workflow delegation methods
- WorkflowDetector.ts: add // +feature: marker for registry scanner
- registry JSON: fix filePath→path + add markerLine to match established schema
- WorkflowForm.tsx: tighten slug pattern ([a-z0-9]+(-[a-z0-9]+)*) + add
  minLength/maxLength to reject consecutive hyphens and enforce 2–64 length
- go.mod: go mod tidy to mark robfig/cron/v3 as direct dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tstapler tstapler force-pushed the stapler-squad-quick-workflows branch from d2ff3d7 to 02cb76f Compare June 12, 2026 01:52
@github-actions

Copy link
Copy Markdown
Contributor

✅ Registry Validation

Registry Validation
===================

Building backend scanner...
Scanning backend features...
Wrote 102 feature files to /tmp/tmp.D7i9VFiQ7n/backend
Wrote 14 feature files to /tmp/tmp.D7i9VFiQ7n/backend
Wrote 22 feature files to /tmp/tmp.D7i9VFiQ7n/backend
Wrote 6 feature files to /tmp/tmp.D7i9VFiQ7n/backend

=== Backend Registry Diff ===
Committed: 136  Generated: 135  Divergence: 0.74%
⚠️  Removed RPCs:
  - upload:image
⚠️  95 feature(s) missing // +api: marker (markerFound: false)

✅ Registry validation passed. Divergence: 0.74%

Test Coverage: 97/136 features have testIds (71.3%)

Registry validation is in observation mode until 2026-05-02.
After that date, divergence > 2% will block merges.
Coverage reporting is advisory only.

@tstapler tstapler merged commit 883deca into main Jun 12, 2026
23 checks passed
@tstapler tstapler deleted the stapler-squad-quick-workflows branch June 12, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants