taskboy 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- taskboy/__init__.py +0 -0
- taskboy/adapters/__init__.py +0 -0
- taskboy/adapters/_util.py +30 -0
- taskboy/adapters/aws_read.py +107 -0
- taskboy/adapters/confluence.py +88 -0
- taskboy/adapters/github_api.py +564 -0
- taskboy/adapters/issues.py +435 -0
- taskboy/adapters/jira.py +263 -0
- taskboy/adapters/sentry.py +82 -0
- taskboy/adapters/slack_history.py +204 -0
- taskboy/assets.py +27 -0
- taskboy/audit.py +78 -0
- taskboy/broker.py +266 -0
- taskboy/classifier.py +219 -0
- taskboy/cli.py +161 -0
- taskboy/config.py +539 -0
- taskboy/dashboard/__init__.py +5 -0
- taskboy/dashboard/api.py +963 -0
- taskboy/dashboard/app.py +78 -0
- taskboy/dashboard/auth.py +117 -0
- taskboy/dashboard/editors.py +140 -0
- taskboy/dashboard/gitops.py +60 -0
- taskboy/dashboard/render.py +48 -0
- taskboy/debug_feed.py +170 -0
- taskboy/deploy/env.example +17 -0
- taskboy/deploy/git-cred-helper.py +36 -0
- taskboy/deploy/install.sh +101 -0
- taskboy/deploy/remote-update.sh +52 -0
- taskboy/deploy/taskboy-restart.path +10 -0
- taskboy/deploy/taskboy-restart.service +8 -0
- taskboy/deploy/taskboy.service +22 -0
- taskboy/hooks.py +208 -0
- taskboy/issue_runs.py +203 -0
- taskboy/llm.py +125 -0
- taskboy/main.py +269 -0
- taskboy/memory.py +59 -0
- taskboy/models.py +103 -0
- taskboy/mrkdwn.py +19 -0
- taskboy/notify.py +41 -0
- taskboy/orchestrator.py +401 -0
- taskboy/personality.py +17 -0
- taskboy/prompts.py +288 -0
- taskboy/quick.py +234 -0
- taskboy/redact.py +49 -0
- taskboy/repocache.py +117 -0
- taskboy/review_requests.py +388 -0
- taskboy/router.py +146 -0
- taskboy/runner.py +581 -0
- taskboy/scheduler.py +301 -0
- taskboy/secrets.py +83 -0
- taskboy/settings.py +19 -0
- taskboy/setup_checks.py +184 -0
- taskboy/setup_wizard.py +699 -0
- taskboy/skills.py +95 -0
- taskboy/slack.py +666 -0
- taskboy/slack_users.py +32 -0
- taskboy/started_messages.py +33 -0
- taskboy/store.py +1393 -0
- taskboy/task_actions.py +86 -0
- taskboy/templates/config.example.yaml +149 -0
- taskboy/templates/conventions.md +60 -0
- taskboy/templates/help.example.md +29 -0
- taskboy/templates/personality_agent.example.md +5 -0
- taskboy/templates/personality_reviewer.example.md +5 -0
- taskboy/templates/services/aws.yaml +7 -0
- taskboy/templates/services/confluence.yaml +4 -0
- taskboy/templates/services/github.yaml +14 -0
- taskboy/templates/services/jira.yaml +6 -0
- taskboy/templates/services/sentry.yaml +4 -0
- taskboy/templates/services/slack.yaml +9 -0
- taskboy/templates/skills/README.md +33 -0
- taskboy/templates/skills/discoverissues/SKILL.md +46 -0
- taskboy/templates/skills/implementapprovedissues/SKILL.md +41 -0
- taskboy/templates/skills/jira2pr/SKILL.md +66 -0
- taskboy/templates/skills/monitor/SKILL.md +32 -0
- taskboy/templates/skills/monitornew/SKILL.md +27 -0
- taskboy/templates/skills/refineissue/SKILL.md +35 -0
- taskboy/templates/skills/release/SKILL.md +36 -0
- taskboy/templates/skills/review/SKILL.md +89 -0
- taskboy/templates/skills/reviewandmonitor/SKILL.md +22 -0
- taskboy/templates/skills/reviews/SKILL.md +26 -0
- taskboy/templates/skills/slack2jira/SKILL.md +47 -0
- taskboy/templates/skills/slack2pr/SKILL.md +64 -0
- taskboy/templates/skills/spec2pr/SKILL.md +35 -0
- taskboy/templates/slack_app_manifest.yaml +48 -0
- taskboy/templates/task_started_messages.yaml +14 -0
- taskboy/ui_dist/assets/index-DmYxR9Qy.css +1 -0
- taskboy/ui_dist/assets/index-LDDO4iT2.js +15 -0
- taskboy/ui_dist/index.html +15 -0
- taskboy/workspace.py +86 -0
- taskboy-0.1.1.dist-info/METADATA +120 -0
- taskboy-0.1.1.dist-info/RECORD +95 -0
- taskboy-0.1.1.dist-info/WHEEL +5 -0
- taskboy-0.1.1.dist-info/entry_points.txt +2 -0
- taskboy-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monitornew
|
|
3
|
+
description: Watch a GitHub repo for the next new PR, checking every 5 minutes for up to 3 hours. When a new PR appears, stop watching and run the /reviewandmonitor procedure on it. Invoked as `@{{agent_name}} /monitornew {repo}` — repo can be `owner/name`, a bare name (assumed {{github_org}}), or a repo URL.
|
|
4
|
+
requires: [reviewandmonitor]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# monitornew
|
|
8
|
+
|
|
9
|
+
Watch a repo and, the moment a new PR is opened, review-and-monitor it. Gives up after 3 hours if none appears (your session has a hard runtime limit — never plan past it).
|
|
10
|
+
|
|
11
|
+
The first repo in the arguments is the repo to watch: `owner/name`, a bare name (→ `{{github_org}}/{name}`), or a repo URL. If it is missing, call `report_blocked` asking for it, then stop.
|
|
12
|
+
|
|
13
|
+
A "new PR" is any PR whose number is higher than the highest PR number that existed when watching started.
|
|
14
|
+
|
|
15
|
+
## The loop
|
|
16
|
+
|
|
17
|
+
Keep watch state in `../notes/monitornew.json`: `end_time` (epoch seconds), `baseline_pr_number`.
|
|
18
|
+
|
|
19
|
+
1. **Baseline** — `mcp__github__list_pull_requests` with state `all`; the highest PR number (0 if none) is `baseline_pr_number`. `end_time` = now + 10800. Write the state file. Post one `report_progress`: `watching {owner}/{repo} for new PRs — checking every 5 min for up to 3h`.
|
|
20
|
+
2. **Tick** — wait 5 minutes using Bash sleeps in chunks no longer than 100 seconds (`sleep 100` three times). Then:
|
|
21
|
+
- If now ≥ `end_time`: stop and write the Final Report (`no new PR in the watch window`).
|
|
22
|
+
- Fetch the highest PR number now. If ≤ `baseline_pr_number`: continue to the next tick.
|
|
23
|
+
- Otherwise: take the **earliest** new PR (lowest number above the baseline), post `report_progress`: `found new PR #{n} — reviewing and monitoring`, and run the included `/reviewandmonitor` procedure on it end to end (its monitor phase inherits your remaining time — watch until your original `end_time`, not a fresh 3 hours).
|
|
24
|
+
|
|
25
|
+
## Final Report
|
|
26
|
+
|
|
27
|
+
Either `no new PR in the watch window`, or which PR was found plus the review-and-monitor results (comment counts, changes seen). Nothing else.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refineissue
|
|
3
|
+
description: Reconcile one issue's discussion with the current target repository and improve the issue for implementation. Invoked as `@{{agent_name}} /refineissue {issue_id}`.
|
|
4
|
+
model: opus
|
|
5
|
+
profile: standard
|
|
6
|
+
internal_tools: [issues]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# refineissue
|
|
10
|
+
|
|
11
|
+
Refine one stored issue using its entire discussion and the current target repository.
|
|
12
|
+
|
|
13
|
+
The first argument is the required issue id. If it is missing or `get_issue` cannot find it, call `report_blocked` and stop.
|
|
14
|
+
|
|
15
|
+
## Step 1 — Reconcile the discussion
|
|
16
|
+
|
|
17
|
+
Call `get_issue` and `list_issue_comments`. Read every top-level comment and reply in order. Resolve comments whose questions have been answered. Reconcile {{agent_name}}'s prior comments: edit a still-useful comment when its wording is stale, delete {{agent_name}}'s obsolete or contradictory comments, and never edit or delete another author's comment.
|
|
18
|
+
|
|
19
|
+
## Step 2 — Verify and enrich
|
|
20
|
+
|
|
21
|
+
Clone the issue's `repo` and re-check the title and markdown description against current code, tests, conventions, and recent history. Correct claims that are stale or inaccurate. Flesh out thin user-submitted issues with concrete files/areas, rationale, acceptance checks, constraints, risks, and relevant evidence.
|
|
22
|
+
|
|
23
|
+
Use the discussion to update the issue's title, description, and priority with `update_issue`. The store may reject updates after implementation has begun; if so, leave the durable issue unchanged and explain the locked state.
|
|
24
|
+
|
|
25
|
+
## Step 3 — Ask what remains
|
|
26
|
+
|
|
27
|
+
Post concise markdown comments for genuinely outstanding questions. Do not repeat answered or already-open questions. Every question should state why the answer changes scope or implementation.
|
|
28
|
+
|
|
29
|
+
## Reply
|
|
30
|
+
|
|
31
|
+
In 1–3 sentences, summarize what was reconciled, what changed, and whether any questions remain.
|
|
32
|
+
|
|
33
|
+
## Final Report
|
|
34
|
+
|
|
35
|
+
Include the issue id/repo, resolved comments, edited/deleted {{agent_name}} comments, title/description/priority changes, repository verification, outstanding questions, and one short line per tool failure.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: Cut a GitHub release of the agent's own repository ({{self_repo}}) with a new semver tag sized to the impact of every change since the last tag, and release notes summarizing them. Invoked as `@{{agent_name}} /release`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# release
|
|
7
|
+
|
|
8
|
+
Publish a GitHub release of the agent's own repo (`{{self_repo}}`): a new `vX.Y.Z` tag plus release notes covering everything merged since the last tag.
|
|
9
|
+
|
|
10
|
+
## Step 1 — Find the last tag and what changed since it
|
|
11
|
+
|
|
12
|
+
Clone the repo if it isn't already in your workspace (`git clone https://github.com/{{self_repo}}`; auth is handled), then:
|
|
13
|
+
|
|
14
|
+
- `git fetch --tags origin`
|
|
15
|
+
- `git tag --list --sort=-v:refname | head -1` — the current latest tag, sorted by semver (not creation date or lexicographically). If there are no tags at all, treat `v0.0.0` as the baseline.
|
|
16
|
+
- `git log <last_tag>..origin/main --oneline` — every commit since it. If this is empty, there is nothing to release: say so and stop; don't create an empty release.
|
|
17
|
+
|
|
18
|
+
## Step 2 — Size the version bump
|
|
19
|
+
|
|
20
|
+
Read the commit summaries (and diffs, if a summary is ambiguous) for everything since the last tag. Pick the **highest** bump triggered by any single change in the batch:
|
|
21
|
+
|
|
22
|
+
- **x** (`vX.0.0`) — a major or large-scale change.
|
|
23
|
+
- **y** (`vX.Y.0`) — a new feature or another larger change.
|
|
24
|
+
- **z** (`vX.Y.Z+1`) — bugfixes and small changes only.
|
|
25
|
+
|
|
26
|
+
A z-bump resets nothing; a y-bump resets z to 0; an x-bump resets y and z to 0. Compute the new tag from the last tag using this rule.
|
|
27
|
+
|
|
28
|
+
## Step 3 — Write the release notes and publish
|
|
29
|
+
|
|
30
|
+
Summarize the changes since the last tag in a short, plain-language body — a few bullet points is usually enough, referencing issue or PR numbers where the commit messages have them. Match the terse, factual tone of existing tag messages (`git show <last_tag>`).
|
|
31
|
+
|
|
32
|
+
Call `mcp__github__create_release` with `repo: {{self_repo}}`, the computed `tag_name`, and the `body` you wrote. Leave `target_commitish` empty so GitHub cuts the tag from the default branch (`main`) rather than whatever branch you happen to have checked out.
|
|
33
|
+
|
|
34
|
+
## Final Report
|
|
35
|
+
|
|
36
|
+
The release URL and the tag it cut, e.g. `v0.1.0: https://github.com/{{self_repo}}/releases/tag/v0.1.0`. If there was nothing to release, say that instead. One short line per failure.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: Review a GitHub pull request for simplicity (KISS + YAGNI) and adversarial scrutiny (correctness, edge cases, security, and tests) against the organization's engineering conventions. Invoked as `@{{agent_name}} /review {pr_url}`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# review
|
|
7
|
+
|
|
8
|
+
Review a pull request through two lenses: **is it as simple as it can be, and does it hold up under adversarial scrutiny?**
|
|
9
|
+
|
|
10
|
+
The first GitHub PR URL in the arguments is the PR to review. If it is missing, call `report_blocked` asking for the PR URL, then stop.
|
|
11
|
+
|
|
12
|
+
Don't pre-check auth or tool availability — just call the tool. If it fails, note it in the Final Report.
|
|
13
|
+
|
|
14
|
+
Never call `report_progress` during a review. Post the PR comments first, then let the completed response be the only Slack message.
|
|
15
|
+
|
|
16
|
+
## The lens: keep it simple
|
|
17
|
+
|
|
18
|
+
- **Did the author do the least?** Could this touch fewer lines, files, or concepts and still satisfy the intent?
|
|
19
|
+
- **No premature structure.** New abstractions, files, folders, config, options "for later," layers of indirection — all suspect; flag what the change doesn't need yet. Repetitive if/else beats a slick abstraction.
|
|
20
|
+
- **No fluff.** Defensive scaffolding, speculative error handling, dead code, drive-by reformatting — all should go.
|
|
21
|
+
- **Longhand over clever/DRY.** Written-out logic is *good*, not a finding — don't push toward cleverness or over-DRYing.
|
|
22
|
+
- **Consistent with the organization's engineering conventions.** Deviations from the conventions document are findings.
|
|
23
|
+
- **Err hard toward "this could be simpler."**
|
|
24
|
+
|
|
25
|
+
The organization's engineering conventions are defined in `{{conventions_file}}`. At runtime the same document is injected into the task workspace as `CONVENTIONS.md`; read it before judging convention adherence when present, and continue without it if absent.
|
|
26
|
+
|
|
27
|
+
### Comments & docstrings — give every one its own pass
|
|
28
|
+
|
|
29
|
+
Scrutinize every added or changed comment and docstring. The bar: short, lowercase, explain *why* or *what's next* — never what the code obviously does; docstrings only on non-obvious methods, one line.
|
|
30
|
+
|
|
31
|
+
**Cut** (say "delete this comment"): restates what the next line plainly does; narrates structure the code already shows (section banners, re-listed params, "this function does X" on an obvious method); duplicates a nearby comment, docstring, or name; changelog/"refactored from…"/TODO-for-this-PR notes; commented-out code.
|
|
32
|
+
|
|
33
|
+
**Keep** (do *not* flag): a short line explaining a non-obvious *why*, a gotcha, a sentinel, or a unit/edge case.
|
|
34
|
+
|
|
35
|
+
**Simplify, don't just delete.** When a bloated comment carries one real nugget of *why*, say "cut to one line: `<the nugget>`" rather than demanding removal.
|
|
36
|
+
|
|
37
|
+
## Step 1 — Read the PR
|
|
38
|
+
|
|
39
|
+
Fetch the PR with `mcp__github__get_pull_request` (title, state, author, head), the changed files with `mcp__github__list_pr_files`, and existing comments and review threads with `mcp__github__list_pr_comments` — one call is enough to see what other human and bot reviewers have already raised. For the full diff and surrounding code, clone the repo with git — auth is handled — and fetch the PR head:
|
|
40
|
+
|
|
41
|
+
git clone https://github.com/{owner}/{repo}
|
|
42
|
+
cd {repo} && git fetch origin pull/{number}/head:pr-{number} && git diff {base_branch}...pr-{number}
|
|
43
|
+
|
|
44
|
+
Read enough of the touched files and their neighbors to judge whether the change fits existing patterns and whether a simpler in-repo approach was available. Skim any linked ticket/thread in the PR body for the actual intent.
|
|
45
|
+
|
|
46
|
+
## Step 2 — Scrutinize for simplicity and robustness
|
|
47
|
+
|
|
48
|
+
For each finding, be concrete: **where** (`file:line` in the new code), **what** (the over-engineering / fluff / convention deviation), **simpler** (the smaller way, ideally the specific edit). Rank findings by impact — how much simplicity they'd buy back or how badly the failure would bite. If the PR is already about as simple as it can be, say so plainly — don't manufacture findings.
|
|
49
|
+
|
|
50
|
+
**Be exhaustive on this pass.** Go file by file through Step 1's changed-files list and check every lens in this step — simplicity, comments and docstrings, and the dimensions below — against each one.
|
|
51
|
+
|
|
52
|
+
Then actively try to break the change. Scrutinize it for:
|
|
53
|
+
|
|
54
|
+
- correctness bugs and silent behavior changes;
|
|
55
|
+
- unhandled inputs, boundary cases, partial failures, and recovery paths;
|
|
56
|
+
- concurrency, ordering, lifecycle, caching, and state-consistency hazards;
|
|
57
|
+
- authorization, data exposure, injection, credential, and other security risks;
|
|
58
|
+
- missing, weakened, misleading, or insufficiently isolated tests;
|
|
59
|
+
- incompatibility with existing APIs, migrations, configuration, or deployed data;
|
|
60
|
+
- departures from established repository patterns and the organization's engineering conventions.
|
|
61
|
+
|
|
62
|
+
Trace important paths end to end. Check assumptions against the code rather than trusting names or comments. For each finding, identify the current file and line, explain the concrete failure mode and impact, and propose the smallest reliable correction. Don't manufacture findings, but never drop a supported finding for politeness.
|
|
63
|
+
|
|
64
|
+
Create one comment per underlying issue. For a rename or repeated pattern, comment once at the defining or clearest location and say it applies everywhere it is used; never repeat the same finding at each occurrence. If another reviewer already raised the same finding, skip it instead of restating it.
|
|
65
|
+
|
|
66
|
+
## Step 3 — Reconcile your prior review comments
|
|
67
|
+
|
|
68
|
+
Before adding anything new, check every comment you left on an earlier pass against the current code. Call `mcp__github__list_pr_comments` with `author: "me"` to pull every prior comment you left, instead of refetching everyone's from Step 1 — keep following the truncation marker's suggested `offset` until it stops appearing, so a long history of your own comments doesn't silently skip reconciliation. Previews are capped at 400 chars and can cut off mid-sentence; when a preview doesn't give you enough to judge, fetch that comment's full body with `comment_id`. Each line's `(resolved)`/`(unresolved)` marker, a thread going "outdated" the moment lines shift, and an author reply of "done" all fail to prove a finding was fixed — check the current code for each:
|
|
69
|
+
|
|
70
|
+
- **Actually fixed** → resolve the thread with `mcp__github__resolve_pr_thread` (pass the comment id) **without replying**, unless it's already marked `(resolved)`.
|
|
71
|
+
- **Not actually fixed, or the fix introduced a new issue** → reply on the thread saying exactly what is still wrong (or what new problem the change created), and re-flag it in this pass's review comments at the current location. There is no unresolve tool, so if the thread is already marked `(resolved)` you cannot and should not try to reopen it — just reply and re-flag. Never resolve a thread you just re-flagged.
|
|
72
|
+
|
|
73
|
+
The rule: "outdated" ≠ resolved, and "done" ≠ done. A finding only closes when the code proves it.
|
|
74
|
+
|
|
75
|
+
## Step 4 — Post the review
|
|
76
|
+
|
|
77
|
+
Post one review with `mcp__github__create_pr_review`: on PRs authored by {{agent_name}}, the only two outcomes are `REQUEST_CHANGES` (any blocking finding) and `APPROVE` (nothing blocking). Cosmetic means comment/docstring wording, naming that isn't a convention deviation, or formatting only; everything else — including simplicity, over-engineering, and any convention deviation — is blocking, and anything ambiguous counts as blocking too. If only cosmetic findings exist (or none), post `APPROVE`; cosmetic findings ride along on the approval as non-blocking notes.
|
|
78
|
+
|
|
79
|
+
On any other PR, use `REQUEST_CHANGES` when a finding is substantive, otherwise `COMMENT`; never `APPROVE`.
|
|
80
|
+
|
|
81
|
+
Include a terse summary body and the findings as inline comments via `comments_json` — one entry per finding, anchored to its file and line.
|
|
82
|
+
|
|
83
|
+
Each inline comment must be one or two plain, high-level sentences: state the problem and the required fix, nothing else. No context narration, multi-paragraph explanations, hedging, softening, or praise padding. If the PR is already simple, post a single short review saying so.
|
|
84
|
+
|
|
85
|
+
## Step 5 — Final response
|
|
86
|
+
|
|
87
|
+
In `## Reply`, use 1–3 casual sentences confirming the review is complete, giving only the high-level gist, and linking the PR. Do not give a rundown of findings; this overrides the general 2–6 sentence Reply guidance.
|
|
88
|
+
|
|
89
|
+
In `## Final Report`, state the number of comments left (or that the PR was already simple), which review outcome was posted, the number of prior threads resolved, the PR link, and any previously-"done" findings that are still open. Use one short line per tool failure. Nothing else.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewandmonitor
|
|
3
|
+
description: Review a GitHub PR for simplicity right now, then keep watching it — re-reviewing on every change for up to 3 hours. Invoked as `@{{agent_name}} /reviewandmonitor {pr_url}`.
|
|
4
|
+
requires: [review, monitor]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# reviewandmonitor
|
|
8
|
+
|
|
9
|
+
Review a pull request now, then keep watching it. A thin chain of the `/review` procedure followed by the `/monitor` procedure on the same PR (both included below).
|
|
10
|
+
|
|
11
|
+
The first GitHub PR URL in the arguments is the PR. If it is missing, call `report_blocked` asking for it, then stop.
|
|
12
|
+
|
|
13
|
+
Two ordered steps, both in this session:
|
|
14
|
+
|
|
15
|
+
1. Run the included `/review` procedure on the PR end to end — it posts the review comments.
|
|
16
|
+
2. Then run the included `/monitor` procedure on the same PR — establish its baseline **after** the review so the loop starts clean.
|
|
17
|
+
|
|
18
|
+
The order matters: reviewing first fills the gap left by monitor's no-review-at-baseline rule.
|
|
19
|
+
|
|
20
|
+
## Final Report
|
|
21
|
+
|
|
22
|
+
The initial review's comment count, then the monitoring summary (ticks, re-reviews). Nothing else.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviews
|
|
3
|
+
description: Run the /review simplicity review across many GitHub PRs — pass a list of PR URLs, or a single repo (URL or bare name) to review every open PR in it. Invoked as `@{{agent_name}} /reviews {pr_url} {pr_url} ...` or `@{{agent_name}} /reviews {repo}`. Use /review for a single PR.
|
|
4
|
+
requires: [review]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# reviews
|
|
8
|
+
|
|
9
|
+
Run the `/review` procedure (included below) across a batch of pull requests.
|
|
10
|
+
|
|
11
|
+
## Step 1 — Figure out which PRs to review
|
|
12
|
+
|
|
13
|
+
Decide from the arguments:
|
|
14
|
+
|
|
15
|
+
- **PR URLs** — one or more GitHub PR URLs (each contains `/pull/<n>`). Review exactly those.
|
|
16
|
+
- **A repo** — a single argument naming a repo, not a PR. Resolve to `owner/repo` (repo URL → take owner/repo; `owner/repo` → as-is; bare name → `{{github_org}}/{name}`). Then `mcp__github__list_pull_requests` with state `open`. Drop drafts and bot-authored PRs. If more than 10 remain, review the 10 most recently updated and list the skipped ones in the Final Report.
|
|
17
|
+
|
|
18
|
+
If the arguments have neither, call `report_blocked` asking what to review, then stop.
|
|
19
|
+
|
|
20
|
+
## Step 2 — Review each PR
|
|
21
|
+
|
|
22
|
+
Work through the PRs **one at a time, in this session** — do not hand work to sub-agents. For each PR, run the included `/review` procedure end to end (read, scrutinize, reconcile, post). After each PR, post one `report_progress` line: `reviewed {repo}#{n} — N comments` (or `already simple`).
|
|
23
|
+
|
|
24
|
+
## Final Report
|
|
25
|
+
|
|
26
|
+
One line per PR: repo#number → comment count or "already simple". One short line per PR whose review failed, so it can be retried. Skipped PRs, if any. Don't restate individual findings — they're inline on each PR.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slack2jira
|
|
3
|
+
description: Read a Slack thread and create a brief, concise Jira ticket on the {{jira_project}} board. Invoked as `@{{agent_name}} /slack2jira {slack_thread_url} {context}` — context describes the epic, story points, and assignee.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# slack2jira
|
|
7
|
+
|
|
8
|
+
Turn a Slack thread into a concise Jira ticket on the **{{jira_project}}** board.
|
|
9
|
+
|
|
10
|
+
- The first URL in the arguments (if any) is the Slack thread to read. When invoked **inside** the target thread, the conversation is already included in your prompt context and no URL is needed.
|
|
11
|
+
- The rest of the arguments are free-form context. Parse it for: the **epic**, **story points**, and **assignee**. Don't require a rigid syntax; read intent.
|
|
12
|
+
|
|
13
|
+
If there is neither a thread URL nor thread context in your prompt, call `report_blocked` asking for the thread, then stop.
|
|
14
|
+
|
|
15
|
+
Don't pre-check auth or tool availability — just call the tool. If it fails, note it in the Final Report.
|
|
16
|
+
|
|
17
|
+
## Step 1 — Read the Slack thread
|
|
18
|
+
|
|
19
|
+
Use the thread conversation already in your prompt when present. If a Slack thread URL was given instead, read it with `mcp__slack__thread_replies` (resolve the channel id and thread timestamp from the URL — `p1234567890123456` becomes `1234567890.123456`).
|
|
20
|
+
|
|
21
|
+
Weight the requester's messages above everyone else's — they define scope and decisions; others are supporting context.
|
|
22
|
+
|
|
23
|
+
Summarize the thread into: the problem/ask, the decision or desired outcome, and any concrete details (repro steps, links, acceptance criteria).
|
|
24
|
+
|
|
25
|
+
## Step 2 — Parse the context argument
|
|
26
|
+
|
|
27
|
+
- **Epic** — match it to an existing epic on {{jira_project}}: `mcp__jira__search_issues` with JQL like `project = {{jira_project}} AND issuetype = Epic AND summary ~ "{hint}"`. If nothing matches confidently, call `report_blocked` asking which epic, then stop.
|
|
28
|
+
- **Story points** — a number.
|
|
29
|
+
- **Assignee** — resolve to a Jira account id with `mcp__jira__search_users`. If it can't be resolved confidently, call `report_blocked`, then stop.
|
|
30
|
+
|
|
31
|
+
## Step 3 — Draft the ticket
|
|
32
|
+
|
|
33
|
+
Keep it super simple — KISS + YAGNI. Title and description matter most; make them as short as possible.
|
|
34
|
+
|
|
35
|
+
- **Title**: one very short line — just enough to name the work.
|
|
36
|
+
- **Description**: keep it minimal, but **always include a `Repositories:` line** naming the repo(s) the work will touch — determine them from the thread and context, matched against the approved repositories; if it genuinely cannot be determined, write `Repositories: unknown`. Put the Slack thread link at the bottom ({{agent_name}} appends its task footer automatically). Add terse step bullets only when there is a clear list.
|
|
37
|
+
- No prose, no fluff, no restating the obvious — but the repositories must always be clear.
|
|
38
|
+
|
|
39
|
+
## Step 4 — Create it on {{jira_project}}
|
|
40
|
+
|
|
41
|
+
Create the issue with `mcp__jira__create_issue`: project `{{jira_project}}`, summary, description, `parent_key` (the epic), `story_points`, and `assignee_account_id`.
|
|
42
|
+
|
|
43
|
+
Create directly when epic, story points, and assignee all resolved cleanly. Call `report_blocked` only when something is ambiguous or missing.
|
|
44
|
+
|
|
45
|
+
## Final Report
|
|
46
|
+
|
|
47
|
+
The ticket URL on its own line. One short line per failure. Nothing else. ({{agent_name}} posts this back to the requesting thread automatically.)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slack2pr
|
|
3
|
+
description: Read a Slack thread and open a PR that implements what it asks for — straight from Slack to a pull request, no Jira ticket in between. Invoked as `@{{agent_name}} /slack2pr {slack_thread_url} {context}` — context may be empty and may name the repo. Simplest thing that works (KISS + YAGNI).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# slack2pr
|
|
7
|
+
|
|
8
|
+
Turn a Slack thread directly into a pull request.
|
|
9
|
+
|
|
10
|
+
- The first URL in the arguments (if any) is the Slack thread to read. When invoked **inside** the target thread, the conversation is already included in your prompt context and no URL is needed.
|
|
11
|
+
- The rest of the arguments are free-form context, possibly empty. Parse for intent — most importantly a **repo hint**, plus any implementation notes.
|
|
12
|
+
|
|
13
|
+
If there is neither a thread URL nor thread context in your prompt, call `report_blocked` asking for the thread, then stop.
|
|
14
|
+
|
|
15
|
+
Don't pre-check auth or tool availability — just call the tool. If it fails, note it in the Final Report.
|
|
16
|
+
|
|
17
|
+
## Keep it simple
|
|
18
|
+
|
|
19
|
+
Simplest thing that works — KISS + YAGNI:
|
|
20
|
+
|
|
21
|
+
- **Do the least** — fewest lines, files, concepts. No new abstractions, files, folders, config, or options "for later" unless the thread genuinely can't be done without them.
|
|
22
|
+
- **No fluff** — no comments restating the code, no docstrings on obvious methods, no defensive/speculative scaffolding.
|
|
23
|
+
- **Match neighboring code** and the repo's existing conventions (see the workspace `CONVENTIONS.md` when present): longhand over clever/DRY. Mirror idioms; don't invent.
|
|
24
|
+
- **Err hard toward under-building** — too-simple gets flagged on review, not the reverse.
|
|
25
|
+
|
|
26
|
+
## Step 1 — Read the thread
|
|
27
|
+
|
|
28
|
+
Use the thread conversation already in your prompt when present. If a Slack thread URL was given instead, read it with `mcp__slack__thread_replies` (resolve the channel id and thread timestamp from the URL — `p1234567890123456` becomes `1234567890.123456`).
|
|
29
|
+
|
|
30
|
+
Weight the requester's messages above everyone else's — they define scope and decisions; others are supporting context.
|
|
31
|
+
|
|
32
|
+
Boil the thread down to: what needs to change, and the smallest change that satisfies it.
|
|
33
|
+
|
|
34
|
+
## Step 2 — Decide the repo
|
|
35
|
+
|
|
36
|
+
The work happens in one of the repos in the `{{github_org}}` org: {{repo_list}}.
|
|
37
|
+
|
|
38
|
+
- If the **context names a repo**, use it.
|
|
39
|
+
- Otherwise infer from the thread (which service/domain the work clearly belongs to).
|
|
40
|
+
- **If you're not confident, call `report_blocked` asking which repo, then stop.** Don't guess on this.
|
|
41
|
+
|
|
42
|
+
The repo may already be cloned in your workspace; otherwise `git clone https://github.com/{{github_org}}/{repo}` (auth is handled).
|
|
43
|
+
|
|
44
|
+
## Step 3 — Implement (simplest possible)
|
|
45
|
+
|
|
46
|
+
1. Branch off **`{{pr_target_branch}}`** (the integration branch and the PR target). Name the branch per {{agent_name}}'s convention: `agent/{task_id}-short-slug`.
|
|
47
|
+
2. Make the **smallest change that satisfies the thread.** Re-read "keep it simple" before you write a line. Prefer editing existing files; prefer a few lines over many.
|
|
48
|
+
3. Follow the repo's existing patterns exactly. No new deps, no version bumps, no reformatting unrelated lines, no drive-by cleanups.
|
|
49
|
+
4. Add a test **only** if the change's logic warrants it, following the repo's testing conventions.
|
|
50
|
+
|
|
51
|
+
## Step 4 — Open the PR
|
|
52
|
+
|
|
53
|
+
Commit, push the branch, and open a PR **targeting `{{pr_target_branch}}`** with `mcp__github__create_pull_request`.
|
|
54
|
+
|
|
55
|
+
- **Title**: short, a terse description of the work, e.g. `fix pagination on the exports list`.
|
|
56
|
+
- **Body**: the required Summary, Testing performed, and Known limitations sections — each terse — plus a link to the Slack thread if you have its URL.
|
|
57
|
+
|
|
58
|
+
Opening the PR automatically triggers a review by {{reviewer_name}}, the reviewer persona (when enabled) — no action is needed, and don't wait for it.
|
|
59
|
+
|
|
60
|
+
Open it directly when the repo and scope are clear. Call `report_blocked` only if the thread is too underspecified to implement.
|
|
61
|
+
|
|
62
|
+
## Final Report
|
|
63
|
+
|
|
64
|
+
The PR URL on its own line. One short line per failure. Nothing else. ({{agent_name}} posts this back to the requesting thread automatically.)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec2pr
|
|
3
|
+
description: Implement one stored issue spec in that issue's repository, open a PR, and record the outcome. Invoked internally as `/spec2pr {issue_id}`.
|
|
4
|
+
model: sonnet
|
|
5
|
+
profile: standard
|
|
6
|
+
internal_tools: [issues]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# spec2pr
|
|
10
|
+
|
|
11
|
+
Implement one issue spec and open a pull request in the issue's target repository.
|
|
12
|
+
|
|
13
|
+
## Step 1 — Load the issue
|
|
14
|
+
|
|
15
|
+
The first argument is the issue id. Call `get_issue`; if it is missing, has no spec, or is not `in_progress`, call `report_blocked` and stop. Treat the returned `repo` as authoritative.
|
|
16
|
+
|
|
17
|
+
## Step 2 — Implement
|
|
18
|
+
|
|
19
|
+
Clone the issue's repo, work only on the assigned `agent/` branch, and never modify or merge a protected branch. Follow the stored spec and the target repository's own conventions.
|
|
20
|
+
|
|
21
|
+
- Make the smallest complete change.
|
|
22
|
+
- Add or update relevant tests and run the repo's validation commands.
|
|
23
|
+
- Open a human-review PR whose title references the issue and whose body includes Summary, Testing performed, and Known limitations.
|
|
24
|
+
|
|
25
|
+
## Step 3 — Record the outcome
|
|
26
|
+
|
|
27
|
+
Call `finish_issue` with `done` and the PR URL on success. If a PR cannot be opened, call `finish_issue` with `failed`, then `report_blocked` with the reason.
|
|
28
|
+
|
|
29
|
+
## Reply
|
|
30
|
+
|
|
31
|
+
Give the PR link or the blocking reason in 1–2 sentences.
|
|
32
|
+
|
|
33
|
+
## Final Report
|
|
34
|
+
|
|
35
|
+
Include the issue id, target repo, PR or blocking reason, validation performed, and one short line per tool failure.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# slack app manifest for the agent harness.
|
|
2
|
+
# create the app at https://api.slack.com/apps -> Create New App -> From an app manifest,
|
|
3
|
+
# pick your workspace, and paste this file. change display_information to your agent's name.
|
|
4
|
+
#
|
|
5
|
+
# keep the scopes in lockstep with what the service actually uses (taskboy/slack.py):
|
|
6
|
+
# intake needs app_mentions:read + the history scopes; replies need chat:write; the 👀 ack
|
|
7
|
+
# needs reactions:write; long replies upload snippets via files:write; DM chat needs im:*;
|
|
8
|
+
# requester display names need users:read + users:read.email.
|
|
9
|
+
#
|
|
10
|
+
# after creating the app:
|
|
11
|
+
# 1. Socket Mode -> Enable, generate an app-level token with connections:write (xapp-...)
|
|
12
|
+
# 2. Install App -> Install to Workspace, copy the bot token (xoxb-...)
|
|
13
|
+
# 3. invite the bot to every channel you plan to allow
|
|
14
|
+
# note: socket mode apps need no public request URL.
|
|
15
|
+
|
|
16
|
+
display_information:
|
|
17
|
+
name: My Agent # <- your agent's display name
|
|
18
|
+
description: Autonomous engineering agent
|
|
19
|
+
background_color: "#0a0f16"
|
|
20
|
+
features:
|
|
21
|
+
bot_user:
|
|
22
|
+
display_name: my-agent # <- shown in slack; usually a lowercase form of the name
|
|
23
|
+
always_online: true
|
|
24
|
+
oauth_config:
|
|
25
|
+
scopes:
|
|
26
|
+
bot:
|
|
27
|
+
- app_mentions:read
|
|
28
|
+
- chat:write
|
|
29
|
+
- channels:history
|
|
30
|
+
- groups:history # needed when any allowed channel is private
|
|
31
|
+
- reactions:write
|
|
32
|
+
- files:write
|
|
33
|
+
- im:history
|
|
34
|
+
- im:write
|
|
35
|
+
- users:read
|
|
36
|
+
- users:read.email
|
|
37
|
+
settings:
|
|
38
|
+
event_subscriptions:
|
|
39
|
+
bot_events:
|
|
40
|
+
- app_mention
|
|
41
|
+
- message.im # direct-message chat
|
|
42
|
+
- message.channels # plain thread replies that answer a blocked task's follow-up questions
|
|
43
|
+
# - message.groups # uncomment when any allowed channel is private
|
|
44
|
+
socket_mode_enabled: true
|
|
45
|
+
interactivity:
|
|
46
|
+
is_enabled: false
|
|
47
|
+
org_deploy_enabled: false
|
|
48
|
+
token_rotation_enabled: false
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
agent:
|
|
2
|
+
- "On it. Starting now."
|
|
3
|
+
- "Task started. I'll report back here as I make progress."
|
|
4
|
+
- "Picked this up — updates will land in this thread."
|
|
5
|
+
- "Working on it. I'll post progress as I go."
|
|
6
|
+
- "Started. Give me a bit and I'll have something to show."
|
|
7
|
+
- "On it now. Watch this thread for updates."
|
|
8
|
+
reviewer:
|
|
9
|
+
- "{reviewer_name} is starting a PR review."
|
|
10
|
+
- "{reviewer_name} picked up a review — comments incoming."
|
|
11
|
+
- "Heads up: {reviewer_name} is reviewing a PR now."
|
|
12
|
+
- "Review underway. {reviewer_name} is reading the diff."
|
|
13
|
+
- "{reviewer_name} took this review task. Verdict soon."
|
|
14
|
+
- "PR review started — {reviewer_name} is on it."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-3xl:48rem;--container-6xl:72rem;--container-7xl:80rem;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-black:900;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.inset-y-0{inset-block:0}.top-0{top:0}.top-1{top:var(--spacing)}.right-0{right:0}.bottom-3{bottom:calc(var(--spacing) * 3)}.-left-\[5px\]{left:-5px}.left-0{left:0}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[5\]{z-index:5}.float-right{float:right}.mx-auto{margin-inline:auto}.my-1\.5{margin-block:calc(var(--spacing) * 1.5)}.my-2{margin-block:calc(var(--spacing) * 2)}.-mt-1{margin-top:calc(var(--spacing) * -1)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-auto{margin-top:auto}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-dvh{height:100dvh}.h-full{height:100%}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[75vh\]{max-height:75vh}.max-h-\[480px\]{max-height:480px}.max-h-\[560px\]{max-height:560px}.min-h-0{min-height:0}.min-h-24{min-height:calc(var(--spacing) * 24)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-48{max-width:calc(var(--spacing) * 48)}.max-w-56{max-width:calc(var(--spacing) * 56)}.max-w-\[85vw\]{max-width:85vw}.max-w-\[180px\]{max-width:180px}.max-w-\[220px\]{max-width:220px}.max-w-\[320px\]{max-width:320px}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.min-w-\[560px\]{min-width:560px}.min-w-\[760px\]{min-width:760px}.min-w-\[860px\]{min-width:860px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[64px_1fr_auto\]{grid-template-columns:64px 1fr auto}.grid-rows-\[0fr\]{grid-template-rows:0fr}.grid-rows-\[1fr\]{grid-template-rows:1fr}.flex-col{flex-direction:column}.flex-row-reverse{flex-direction:row-reverse}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[3px\]{border-radius:3px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-md{border-top-right-radius:var(--radius-md)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-8{padding:calc(var(--spacing) * 8)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-px{padding-block:1px}.pt-1{padding-top:var(--spacing)}.pt-2\.5{padding-top:calc(var(--spacing) * 2.5)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-1{padding-right:var(--spacing)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-\[0\.92em\]{font-size:.92em}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[16px\]{font-size:16px}.text-\[17px\]{font-size:17px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[26px\]{font-size:26px}.text-\[30px\]{font-size:30px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.22em\]{--tw-tracking:.22em;letter-spacing:.22em}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.accent-cyan-500{accent-color:var(--color-cyan-500)}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-100{opacity:1}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-\[grid-template-rows\]{transition-property:grid-template-rows;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.outline-none{--tw-outline-style:none;outline-style:none}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media(hover:hover){.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:bg-white\/\[0\.02\]:hover{background-color:#ffffff05}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.02\]:hover{background-color:color-mix(in oklab,var(--color-white) 2%,transparent)}}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-cyan-500:focus{border-color:var(--color-cyan-500)}.focus\:border-cyan-600:focus{border-color:var(--color-cyan-600)}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}@media(min-width:40rem){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:ml-8{margin-left:calc(var(--spacing) * 8)}.sm\:block{display:block}.sm\:hidden{display:none}.sm\:table-cell{display:table-cell}.sm\:w-64{width:calc(var(--spacing) * 64)}.sm\:w-72{width:calc(var(--spacing) * 72)}.sm\:w-\[40vw\]{width:40vw}.sm\:w-auto{width:auto}.sm\:min-w-\[26rem\]{min-width:26rem}.sm\:flex-none{flex:none}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:py-6{padding-block:calc(var(--spacing) * 6)}}@media(min-width:48rem){.md\:absolute{position:absolute}.md\:static{position:static}.md\:inset-x-auto{inset-inline:auto}.md\:inset-y-auto{inset-block:auto}.md\:right-0{right:0}.md\:bottom-auto{bottom:auto}.md\:z-10{z-index:10}.md\:z-20{z-index:20}.md\:z-auto{z-index:auto}.md\:mt-1{margin-top:var(--spacing)}.md\:flex{display:flex}.md\:hidden{display:none}.md\:h-dvh{height:100dvh}.md\:max-h-80{max-height:calc(var(--spacing) * 80)}.md\:w-56{width:calc(var(--spacing) * 56)}.md\:w-\[min\(22rem\,90vw\)\]{width:min(22rem,90vw)}.md\:min-w-0{min-width:0}.md\:flex-1{flex:1}.md\:translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bg-transparent{background-color:#0000}.md\:p-2{padding:calc(var(--spacing) * 2)}.md\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:transition-none{transition-property:none}}@media(min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}}}:root{color-scheme:dark;--page:#0a0f16;--surface-1:#101826;--surface-2:#16202f;--hairline:#94c5e924;--hairline-strong:#94c5e947;--text-primary:#e8eef6;--text-secondary:#9db0c3;--text-muted:#64788c;--grid:#1b2534;--axis:#2b3a4d;--accent:#22d3ee;--accent-dim:#22d3ee24;--series-sonnet:#0891b2;--series-opus:#d97706;--series-fable:#8b5cf6;--series-haiku:#059669;--series-other:#64748b;--status-good:#0ca30c;--status-warning:#fab219;--status-serious:#ec835a;--status-critical:#d03b3b;--status-neutral:#64748b;--mono:ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace}html,body{background:var(--page);color:var(--text-primary);font-family:system-ui,-apple-system,Segoe UI,sans-serif;font-size:14px}.panel{background:var(--surface-1);border:1px solid var(--hairline);border-radius:10px}.panel-title{letter-spacing:.14em;text-transform:uppercase;color:var(--text-secondary);font-size:11px;font-weight:600}.mono{font-family:var(--mono)}.tnum{font-variant-numeric:tabular-nums}.glow-dot{box-shadow:0 0 6px 1px}@keyframes pulse-soft{0%,to{opacity:1}50%{opacity:.35}}.pulse{animation:1.6s ease-in-out infinite pulse-soft}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{border:2px solid var(--page);background:#223146;border-radius:6px}::-webkit-scrollbar-track{background:0 0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}
|