orboto 0.173.0 → 0.174.1

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.
package/dist/SKILL.md DELETED
@@ -1,476 +0,0 @@
1
- ---
2
- name: orboto
3
- description: Read this BEFORE any orboto-related work - creating tickets, claiming them, status moves, time tracking, comments, milestones, releases, or any answer to a question about how orboto's workflow works. Without this skill loaded the agent doesn't know the ticket-first rule, the `scripts/orboto.mjs` wrapper, the per-project status enum, or the commit/release etiquette. Operates as a regular orboto team member account; all permissions + ACLs enforced. On-demand reference lives in companion files next to SKILL.md (REFERENCE.md = full operation catalogue, DOCS-WIKI.md = docs/wiki, ADVANCED.md = daemon/multi-agent/OQL/primer) - read them when a task calls for it. Run `scripts/orboto.mjs init` once per repo to drop a CLAUDE.md / AGENTS.md pointer so future sessions auto-load this skill on session start.
4
- compatibility: Requires Node.js 18+ (native fetch), `jq` for the operation examples, and network reach to an orboto API instance. Auth via bot account + `api:use`-scoped API key.
5
- license: MIT
6
- metadata:
7
- version: "0.173.0"
8
- homepage: https://github.com/orboto/orboto
9
- ---
10
-
11
- # orboto - Agent Skill
12
-
13
- orboto is a ticket + project management platform (full data model in `CLAUDE.md` at repo root). This skill lets an AI agent participate as a **team member** - dev and/or PM - via the REST API.
14
-
15
- Key rule: you are **never** a superuser. Every call is authenticated, every permission is enforced. What the account behind `ORBOTO_TOKEN` cannot do in the web UI, you cannot do either.
16
-
17
- ---
18
-
19
- ## Setup
20
-
21
- Two env vars drive everything:
22
-
23
- - `ORBOTO_BASE_URL` - e.g. `https://orboto.company.com` (no trailing slash, no `/api` suffix)
24
- - `ORBOTO_TOKEN` - API key starting with `orb_…` (preferred) **or** a JWT from `POST /auth/login`
25
-
26
- **Credential file (recommended for local dev):** place a `.env` file in the skill root directory (`scripts/../.env`) and the wrapper loads it automatically if the env vars are not already set:
27
- ```
28
- ORBOTO_BASE_URL=http://localhost:3000
29
- ORBOTO_TOKEN=orb_xxxxxxxxxxxx
30
- ```
31
- The `.env` file should be gitignored - never commit credentials.
32
-
33
- When the skill is downloaded from an orboto instance (`/skills/orboto/download` or `self-update`), a pre-rendered `.env.sample` ships alongside the skill with `ORBOTO_BASE_URL` already filled in for that instance's public URL - copy it to `.env` and paste your token. Self-update never overwrites an existing `.env`, it only refreshes `.env.sample` when the source instance's URL or template changes.
34
-
35
- **Getting an API key** (operator, one-time):
36
- 1. Admin → Users → "New user" → enable **Service account** (creates a bot - cannot log in via password)
37
- 2. Edit user → Access → assign **service-account** role (or a custom role with `api:use`)
38
- 3. Edit user → API keys → Generate → **copy the secret once** (it's shown exactly once)
39
- 4. Set `ORBOTO_TOKEN=orb_xxxxxxxxxxxx` in your `.env` or shell environment
40
-
41
- **The `orboto` wrapper - use this instead of curl.** A thin Node wrapper ships at `scripts/orboto.mjs` inside this skill. It handles the auth header, JSON in/out, credential loading, and all shortcuts. Prefer it over hand-written `curl` - one Bash allowlist entry covers every operation, instead of each `curl` triggering a permission prompt.
42
-
43
- **Paths in this file are relative to the skill root** per the Agent Skills spec. When you actually invoke the wrapper from a Bash tool, prepend the skill's installation path - wherever your agent keeps skills (Claude Code: `.claude/skills/orboto/scripts/orboto.mjs` for a project install or `~/.claude/skills/orboto/scripts/orboto.mjs` for user-level; other agents such as Codex or Cursor use their own skills directory). The examples below use the short `scripts/orboto.mjs` form for readability.
44
-
45
- **Smoke test**:
46
- ```bash
47
- scripts/orboto.mjs whoami
48
- ```
49
- Expected: JSON with your bot's `id`, `email`, `fullName`, `isBot: true`. On `401 API access not permitted` the account is missing the `api:use` permission - ask the operator to fix the role, not to issue a new key.
50
-
51
- **One-time per repo - drop a session-start pointer:**
52
- ```bash
53
- scripts/orboto.mjs init # auto: refresh whichever file exists
54
- scripts/orboto.mjs init --target agents # or: claude | both
55
- ```
56
- Writes a short `## orboto` block (~190 tokens) telling future AI sessions to run `session-start` before any orboto operation. Without that pointer, agents see the skill listed but never read its body until they invoke it.
57
-
58
- **Target selection (ORB-1657).** `--target auto` (the default) refreshes whichever of `CLAUDE.md` / `AGENTS.md` already exists, and writes both only when neither does. Pass `claude`, `agents` or `both` to decide explicitly - writing a file the project does not use is pure per-session tax.
59
-
60
- **The block deliberately carries no rules.** It points at `session-start`, which returns the complete current rule set and caches by rules-hash so repeat calls are cheap. Baking a snapshot in (the pre-ORB-1657 behaviour) cost ~5.4k tokens per file in every session and duplicated exactly what `session-start` already knows how to skip. `--with-rules` restores it for genuinely offline agents that cannot call the API - it is opt-in for that reason, not a convenience.
61
-
62
- Re-running refreshes the block in place (replaces between the markers), so a skill update propagates to existing repos **and shrinks an old fat block** - verified: 22.976 characters down to 845, surrounding file content untouched.
63
-
64
- ---
65
-
66
- ## Discovery - always do this first
67
-
68
- Pull the live OpenAPI spec before guessing schemas:
69
- ```bash
70
- scripts/orboto.mjs get /docs/json > /tmp/orboto-openapi.json
71
- ```
72
- `jq '.paths | keys' /tmp/orboto-openapi.json` gives you every endpoint. When in doubt, read the spec - this file may drift; the spec is the source of truth.
73
-
74
- Orient yourself with these calls (all via `scripts/orboto.mjs`):
75
- - `scripts/orboto.mjs whoami` → your account. The `workspaceLocale` field is the language this workspace expects content in - **write every ticket, comment and doc in that language.** Check it before a mass-create; don't wait for the after-the-fact language-mismatch warning. The project primer header echoes the same value.
76
- - `scripts/orboto.mjs get /projects` → projects you're a member of (you only see these)
77
- - `scripts/orboto.mjs get /projects/<id>` → members, milestones, labels, versions, budget
78
- - `scripts/orboto.mjs get /projects/<id>/ticket-statuses` → **this project's** workflow (statuses are per-project, not global). You need the `id` of the target status to move a ticket; never hardcode status names.
79
-
80
- **Ticket language consistency.** The expected language is resolved per project: a project can set its own `language`, which wins over the workspace language; unset projects inherit the workspace language. The project primer header always shows the effective one for that project. A `languageWarning` on create/update is a signal, not noise: "non-blocking" means the write wasn't prevented, not that the consequence is irrelevant. Mixed-language tickets degrade full-text search, duplicate-detection, and Ask-Docs. If the same warning repeats across a batch, stop at the second occurrence and clarify the intended language with the operator instead of pushing the whole batch through. The warning object carries `code` + `severity` (`warn` | `block`). When a workspace turns on **strict enforcement**, a mismatch comes back as `severity: "block"` and the create/update is rejected with HTTP 422 - rewrite in the workspace language, or (only when the language is genuinely intentional) retry with `--allow-language-mismatch` on the wrapper / `allowLanguageMismatch: true` on the MCP tool.
81
-
82
- ### Wrapper shortcuts
83
-
84
- All shortcuts accept both `--flag=value` and `--flag value` forms interchangeably. Unknown flags now error out with a Levenshtein-1 suggestion instead of silently dropping.
85
-
86
- **Read / query:**
87
- - `scripts/orboto.mjs ticket ORB-42` → compact card (~400 bytes). Use `--full` for raw JSON.
88
- - `scripts/orboto.mjs my-tickets [--project ORB] [--limit 50]` → compact list of your assigned tickets
89
- - `scripts/orboto.mjs list-tickets <projectKey> [--status todo|in_progress|in_review|done] [--limit 50]` → compact ticket list for a project
90
- - `scripts/orboto.mjs list-projects [--query <substr>] [--limit 50]` → compact project list (key/name/status) + `{ total, totalProjects }`; pass `--query` to filter instead of dumping every project. You can usually use a project key directly without listing.
91
- - `scripts/orboto.mjs search <query> [--types=ticket,doc,comment] [--project=ORB] [--limit=10]` → unified full-text search
92
- - `scripts/orboto.mjs search-docs "<query>" [--project=ORB] [--space=<id>] [--limit=20]` → snippet-level doc search (highlighted passage + section anchor, not the full doc body)
93
- - `scripts/orboto.mjs edit-doc <docId> --old "<s>" --new "<s>" [--all] [--base <revId>]` → targeted string-replace doc edit (ships only the diff)
94
- - `scripts/orboto.mjs milestones <projectKey>` / `statuses <projectKey>` / `labels <projectKey>` → metadata listings (UUID + name per row; milestones also show their key, e.g. `ORB-M3`)
95
- - `scripts/orboto.mjs critical-path <projectKey> [--milestone "Name"] [--include-closed-milestones]` → Critical Path Method: the critical dependency chain + each ticket's slack (total float, working days). Durations from estimates (8h/day, min 1). A dependency cycle is reported instead of a bogus path. Tickets in completed/archived milestones are hidden by default; `--include-closed-milestones` opts in.
96
- - `scripts/orboto.mjs analytics <projectKey> <report> [--milestone "Name"] [--mode hours|money]` → project analytics + Earned Value. `report` = overview / burndown / velocity / cycle-time / workload / budget / collaboration / earned-value / estimation-accuracy / flow-time / flow-metrics / forecast / bottleneck. `collaboration` = human-only / agent-only / mixed ticket classification from the agent-work stamps, agent share of effort per project / milestone / member, weekly trend (`--milestone` scopes). `bottleneck` = longest-dwell status + trend, delivery predictability (lead/cycle variance per cohort), worst-aging open tickets. `forecast` = Monte-Carlo delivery forecast (p50/p85/p95 "done by X" from throughput; `--milestone` scopes the remaining set). `flow-metrics` = Kanban flow (WIP, weekly throughput, flow efficiency, aging WIP, cumulative flow diagram). `flow-time` shows lead vs cycle vs effort side-by-side as median + p75/p90 (not just mean), split by cohort/size/type. `estimation-accuracy` is the estimate-vs-actual calibration (multiplier + confidence per agents/humans/combined; degrades through tracked-effort → cycle-time → lead-time and reports insufficient rather than inventing a number) - use estimation-accuracy + flow-time for grounded effort/duration answers, not cycle-time/velocity. Gated server-side: `analytics:view` for charts, `budget:view` for `budget` + earned-value `--mode money` (otherwise 403). MCP: `orboto_analytics`.
97
- - `scripts/orboto.mjs raci <projectKey> [--milestone "Name"] [--epics-only]` → the RACI matrix (tickets x members, cells = R/A/C/I). **RACI is opt-in per project** - only use it (and only mention RACI at all) when the project has `raciEnabled` true; never raise RACI on a project that hasn't opted in. Empty when off. MCP: `orboto_raci`.
98
- - `scripts/orboto.mjs timer` → current running timer state
99
-
100
- **Ticket lifecycle:**
101
- - `scripts/orboto.mjs claim ORB-42` → **adds yourself** as an assignee (existing assignees stay) + move to `in_progress` + start timer. Idempotent: rerunning on a ticket you already own does nothing destructive. If a timer is already running on a **different** ticket, it's auto-stopped (with an auto-note) before the new one starts - no more stale timers across claims. Flags: `--sole` removes every other assignee first (destructive take-over); `--force` allows reopening a `done` ticket; `--no-timer` skips the timer step entirely.
102
-
103
- **Multi-instance agents on a bot / service account (ORB-1252):** this works **automatically - no setup**. When the account is a bot/service account, `claim` / `close` / `timer-start` / `timer-stop` scope the timer to your instance (auto-derived from the process, so multiple agents under one shared API key never share or stomp a timer): every instance tracks its own ticket concurrently, and there is **NO auto-stop** - you own the timer (claim starts it, close/timer-stop ends it; it will not auto-stop when you claim another ticket, so stop it explicitly before switching). Human accounts keep the legacy single-timer + auto-stop behaviour. Optional: set `ORBOTO_AGENT_SESSION=<name>` only if you want an explicit, human-readable instance label instead of the auto id.
104
- - `scripts/orboto.mjs unclaim ORB-42` → unassign self + move back to `todo` (opposite of claim)
105
- - `scripts/orboto.mjs move ORB-42 in_review` (category: `todo` / `in_progress` / `in_review` / `done`). **Returns `summaryWarning` (ORB-1332)** when the move lands in `in_review`/`done` and you posted no summary comment in the last few minutes - non-blocking (the move succeeds), printed on STDERR. Post what changed, the commit SHA, and how to verify.
106
- - `scripts/orboto.mjs close ORB-42 [--comment "..."]` → move to `done`; optional comment posted first. Passing `--comment` doubles as the transition summary and suppresses the `summaryWarning`; closing without one (and none in the last few minutes) prints a non-blocking `summaryWarning` (ORB-1332). If the active timer is on THIS ticket it's auto-stopped with a note (so time doesn't keep accruing against a closed ticket). Timers on other tickets are left alone. `--no-timer` skips the auto-stop.
107
- - `scripts/orboto.mjs delete-ticket ORB-42 --force` → **DESTRUCTIVE, IRREVERSIBLE** hard-delete (row + history gone, fires a `ticket.deleted` webhook). Requires `--force`. **Strongly prefer `move ORB-42 wont_fix`** for duplicate / superseded / wrong-project / out-of-scope tickets - that keeps history + analytics. Only hard-delete a ticket that should truly never have existed (accidental dupe, spam).
108
- - `scripts/orboto.mjs comment ORB-42 "short status note"`
109
- - **Edit / delete a comment** (ORB-1285): comments are NOT append-only - fix a wrong one. MCP: `orboto_update_comment` / `orboto_delete_comment` (pass the `commentId` from `orboto_get_ticket`'s comments). Wrapper: `patch /tickets/:id/comments/:commentId '{"content":"…"}'` and `delete /tickets/:id/comments/:commentId`. You can always edit/delete your OWN comment; editing another's needs super-admin, deleting another's needs `comment:delete_others`.
110
- - `scripts/orboto.mjs patch-ticket ORB-42 <field> <value>` → update a single field (title, description @file or --stdin, priority, status, milestone name, dueDate, etc.)
111
- - `scripts/orboto.mjs attachments ORB-42` → list a ticket's attachments (id, filename, size, type). `scripts/orboto.mjs download-attachment <id> [outPath]` → fetch the bytes (auth + ACL base64 route) to disk so you can read the image/file. `orboto_get_ticket` (MCP) also surfaces the attachments array; MCP `orboto_get_attachment` returns an image content block so the model VIEWS a screenshot directly (ORB-1455).
112
-
113
- **Bulk operations** - apply the same change to N tickets in one invocation instead of a bash loop. Ticket list accepts `ORB-1,ORB-2,ORB-3`, `--from=-` (stdin, one per line), or `--from=@file.txt`. Add `--dry-run` to preview, `--force` when touching > 50 tickets.
114
- - `scripts/orboto.mjs bulk-patch <keys> <field> <value>` - same field update
115
- - `scripts/orboto.mjs bulk-move <keys> <category>` - move many
116
- - `scripts/orboto.mjs bulk-close <keys> [--comment "..."]` - close many + optional shared comment
117
- - `scripts/orboto.mjs bulk-comment <keys> <text>` - post same comment on each
118
- - `scripts/orboto.mjs bulk-assign <keys> <email-or-userId>` / `bulk-unassign <keys> <email-or-userId>`
119
-
120
- Progress appears on stderr (`[3/9] ORB-152 → ok`); exit 0 if all succeed, 1 if any failed. Failures don't abort the batch - they're collected and reported at the end.
121
-
122
- **Create (PM workflow):**
123
-
124
- > **⚠️ STOP - duplicate check is mandatory before ANY `create-ticket`.** Multiple agents and humans share every project; an existing ticket for "the same scope" is the default state, not the exception. Skipping this step is how parallel tickets covering the same work end up on the board - planning context evaporates, downstream tickets reference both, and the original scope gets silently dropped.
125
- >
126
- > **Fast path (recommended, ORB-831):** run `scripts/orboto.mjs check-similar <projectKey> "<intended title>"` first. The system returns the same candidate set the create-time safety-net would warn about, ranked by tsvector + AI-embedding similarity. **When drafting a child under an epic, pass `--parent <epicKey> --type <type>` (ORB-1604)** - the parent, its other children and epics then come back labelled `(related: parent/sibling/epic)` instead of being miscounted as duplicates. Possible recommendations:
127
- >
128
- > - `safe to create` - no candidates above threshold. Proceed.
129
- > - `Only related context found` - the hits are your parent/siblings/epics, not duplicates. Proceed; link them.
130
- > - `Possible related tickets` - review the list before deciding scope.
131
- > - `HIGH-SIMILARITY MATCH FOUND` (top hit ≥ 90%) - almost certainly a duplicate. Extend / comment on the existing ticket instead.
132
- >
133
- > **How to query so you actually find the dup, and the full belt-and-braces procedure for mid-range matches / epic sub-tickets: `CONVENTIONS.md` → "Before any create-ticket" (ORB-1121).** Read it before concluding "no dup" from a 0-result search - that conclusion is the single most common way this check fails.
134
- >
135
- > **Belt-and-braces safety-net (ORB-831, always on):** even if you skip both procedures above, `create-ticket` runs the same similarity check server-side and returns a `similarWarnings` array on the response. The wrapper prints those warnings on STDERR; the MCP `orboto_create_ticket` surfaces them in the tool response. When warnings appear after a create, **review the candidates and close the new ticket as a duplicate** of the listed match if covered. The advisory safety-net never refuses a create - it's a last line of defence, not a substitute for the pre-check.
136
- >
137
- > **Hard duplicate-block (ORB-1471) - this one DOES refuse.** A workspace can set `duplicate_block_threshold` (0 = off, recommended 0.90). When it's on and the top similarity match meets/exceeds it, `create-ticket` is **rejected with HTTP 409** (exit 1) carrying the candidate list - the ticket is NOT created. This is enforcement, not advice: extend or comment on one of the listed tickets instead. If you have genuinely confirmed none of them cover the work, retry with `--allow-duplicate --duplicate-justification "why it is not a duplicate"` on the wrapper (or `allowDuplicate: true` + `duplicateJustification` on the MCP / chat tool); the justification is persisted as a comment on the new ticket. The block is skipped automatically while a project is under a create burst (the deferred advisory comment still runs), so it can't stall a bulk import.
138
- >
139
- > Failure mode to remember: when in doubt, **paste the title into the operator chat first** and ask "exists this already?" before running `create-ticket`. One round-trip beats a duplicate that lives forever.
140
-
141
- ```bash
142
- scripts/orboto.mjs create-ticket <projectKey> <title> \
143
- [--type task|bug|story|epic] # default: task
144
- [--priority blocker|high|normal|low|trivial] # default: normal
145
- [--milestone "<MS-key | Name | UUID>"]
146
- [--assign userId-or-email] # repeatable for multiple assignees
147
- [--label NAME] # repeatable; resolves by name on the project
148
- [--parent <ticketKey|UUID>] # parent epic/story
149
- [--due YYYY-MM-DD]
150
- [--private]
151
- [--description "text" | --description-stdin]
152
- ```
153
- Output includes `id` (UUID), `ticketKey`, `title`, `status`, and `url` - use `id` directly for follow-up calls without an extra lookup.
154
-
155
- > **⚠️ Never write `description: "@/tmp/..."` or any local-path reference into a ticket.** The orboto API stores the field as literal text, and your `/tmp` directory is only visible to you - the operator and the rest of the team see a broken-looking placeholder instead of the actual description. Use `--description "..."` with the Markdown inline, or `--description-stdin` piping from a heredoc. The only acceptable `@file` reference is a path that exists in the checked-in repo (e.g. `@docs/ticket-templates/bug.md`). If you ever find yourself tempted to write to `/tmp` first and then reference it, just write the Markdown directly into the flag or stdin.
156
-
157
- **Assign / milestone:**
158
- - `scripts/orboto.mjs assign ORB-42 <userId-or-email>` → resolves email to userId automatically
159
- - `scripts/orboto.mjs unassign ORB-42 <userId-or-email>`
160
- - `scripts/orboto.mjs set-milestone ORB-42 <MS-key | "Name" | UUID>` → resolves key/name to UUID automatically
161
-
162
- **Timer:**
163
- - `scripts/orboto.mjs timer-start ORB-42`
164
- - `scripts/orboto.mjs timer-stop "what you did"`
165
-
166
- **Correcting a time entry** (ORB-1292) - time entries are NOT additive-only; fix a wrong/over-tracked one instead of logging more on top. `log-time` only adds. List, then edit or delete (own entries always; another user's delete needs `time:delete_others`; entries locked by an approved timesheet are read-only):
167
- ```bash
168
- scripts/orboto.mjs get /tickets/<ticketUuid>/time-entries # find the entry id + duration
169
- scripts/orboto.mjs patch /tickets/<ticketUuid>/time-entries/<id> '{"durationMinutes":45}'
170
- scripts/orboto.mjs delete /tickets/<ticketUuid>/time-entries/<id>
171
- ```
172
- MCP equivalents: `orboto_list_time_entries`, `orboto_edit_time_entry`, `orboto_delete_time_entry`.
173
-
174
- **Raw API (for anything not covered by shortcuts):**
175
- ```bash
176
- scripts/orboto.mjs get|post|patch|put|delete <path> [json-body]
177
- ```
178
-
179
- **Backups** (admin:backup:export / read) - create + download a full workspace backup (DB + storage) in one shot, list runs, download a stored run:
180
- ```bash
181
- scripts/orboto.mjs post /admin/backup/full > backup.zip # on-demand full backup, streams the ZIP
182
- scripts/orboto.mjs get /admin/backup/runs # list runs (scheduled + ad-hoc)
183
- scripts/orboto.mjs get /admin/backup/runs/<runId>/download > x.zip # download a stored run
184
- ```
185
- MCP equivalents (ORB-1301): `orboto_create_full_backup`, `orboto_list_backups`, `orboto_download_backup` (return the ZIP as a base64 resource attachment); `orboto_trigger_backup` runs a pre-configured named job.
186
-
187
- ---
188
-
189
- ## The rhythm - orient here before your first ticket
190
-
191
- orboto-native agent work has a specific tempo. Internalise these six habits and the rest of the skill reads as implementation detail.
192
-
193
- 1. **Ticket first, code second.** Every change the operator asks for - fix, feature, refactor, tweak - gets a ticket created BEFORE you touch code. A `claim` on an existing ticket counts; a silent commit does not. The only exception is an incremental improvement on a ticket you're already working on.
194
- 2. **Claim = assign + status + timer; stop the timer the moment you finish.** `scripts/orboto.mjs claim ORB-42` self-assigns, flips the ticket to `in_progress`, and starts the time tracker - every hour on a claimed ticket should show up in analytics. **The moment you finish the work, STOP the timer** (`timer-stop` / `close`) **before anything else** - before you answer, before you ask a question, before you claim the next ticket. A still-running timer silently inflates the ticket's tracked time, and claiming the next ticket re-attributes the running time to the wrong ticket. If an entry is already over-tracked, **correct it** (edit it to the right duration or delete it - see *Time tracking*) - never `log-time` more on top, which is additive and only makes the total worse.
195
- 3. **One commit per task.** Never bundle two logically-distinct tickets into one commit, and never split a single ticket into "mini-commits per file". The commit and the ticket are 1:1 - that keeps reverts surgical and the changelog readable.
196
-
197
- **This is the `implementation`/`docs` `deliveryMode` policy, not a universal law (ORB-1608/1642).** Every ticket carries a `deliveryMode` (`--delivery-mode` on `create-ticket`, `deliveryMode` on `patch-ticket`) - default `implementation` for most types, `epic` for `--type epic`. `implementation`/`docs` tickets expect exactly one linked commit and closing without one prints a non-blocking warning - UNLESS the project has no git connection at all, in which case there's nothing to print (a link isn't possible). With a connection: `no_commit_linked` means the connection looks healthy, so link a commit; `git_delivery_failing` means the connection itself looks broken - a commit may already exist and just hasn't arrived, check the connection (Project Settings -> Git) before assuming none was made. `review` (read-only preflights), `admin` (ops work with external evidence), and `epic` (completion derives from children) tickets never expect a commit - set the mode instead of inventing a decision-log commit to satisfy the rule.
198
- 4. **Ticket key in the commit subject.** Format: `type(scope): description (ORB-42)` - Conventional-Commits prefix in front, ticket key in parens at the END. That's what the orboto git parser looks for (see *Workflow A* step 5 for the full reference table). A commit without a key never gets linked to its ticket; the activity panel stays empty.
199
- 5. **Push after every commit on the branch you work on.** The remote is what the team sees. Local-only commits are invisible. The only branches you never push yourself are *release* and tags - those move only on explicit operator instruction.
200
- 6. **Transition with a summary comment.** Never move a ticket to `in_review` or `done` silently. One paragraph: what changed, commit SHA, how a reviewer can verify in the UI. The comment is what a human reads cold - not your diff.
201
- 7. **Checklists for one-liners, sub-tickets for real steps - and only when work actually starts.** Two progress primitives, plus a third question (when to actually create them):
202
- - **Checklist items** are for acceptance-criteria-shaped one-liners inside a single ticket's scope: "write a test for the happy path", "update the changelog", "migrate the prod config". They live on the parent ticket, no separate description / time tracking / review. The `ticket` card shows `Activity: checklists N/M` and the Kanban card shows a `done/total` badge.
203
- - **Sub-tickets** are for anything big enough to deserve its own commit, its own time tracker, or its own review. Independent vertical slices of a feature, anything assignable to a different person. Create via `create-ticket ... --parent <epicKey>` and they link bidirectionally - the parent's `ticket` card shows `Children: N sub-tickets (M open) - ACME-12, ACME-13, ACME-14`, each child's card shows `Parent: [ACME-10] …`.
204
- - **Phase epics** are a separate primitive for the multi-phase shape of a larger feature: `Phase 1 MVP`, `Phase 2 expansion`, `Phase 3 long-term`. One epic per phase, all sitting in a single milestone (the milestone is the feature). Each phase epic is the parent of its own sub-ticket tree once that phase activates; until then the sub-tree lives in the phase epic's description. **Phase epics stay materialised in the ticket system from the day the feature is planned**, even when their sub-trees do not - they are the visible roadmap signal anyone reading the backlog sees, including external agents and team members without repo access.
205
-
206
- Rule of thumb (which primitive): if completing the step naturally produces a separate commit (or a half-day of work, or needs its own status column on the board), it's a sub-ticket. Otherwise checklist. Never force 500-LOC phases into a checklist to avoid creating tickets - the board and the burndown lose their signal.
207
-
208
- Rule of thumb (when to materialise): only create **sub-tickets** and checklist items the moment the parent epic is being **actively worked**, i.e. claimed and in_progress. Their phase plan stays **inside the parent's description** until then, not as five empty TODO sub-tickets cluttering `my-tickets` for everyone. Materialise the sub-tree when work on that branch starts - usually as the first action after `claim`.
209
-
210
- **What this rule does NOT defer: top-level phase epics.** Multi-phase features get an epic per phase **from the moment the feature is planned**, even if the later phases are months away. Phase epics are the roadmap signal *in the ticket system* - anyone reading the backlog (external agents, team members without repo access, AI tools that only see orboto) learns what scope is on the table from those epics. Strategy docs supplement; **tickets define scope**. Three primitives, three timings, one principle: the ticket system is the contract.
211
-
212
- For your own work: look at the card summary when you claim. `Activity: checklists 3/12` tells you there's a list to tick off as you go. `Children: 5 sub-tickets (2 open)` tells you this is an epic and the open children are the real next actions. Half-ticked lists or mixed-mode epics (checklist items that duplicate sub-tickets) are the tell-tale signs the previous owner didn't keep this rule.
213
-
214
- 8. **Big features get their own milestone - created FIRST.** Whenever the work you're about to file looks like an Epic plus three or more phase tickets (multi-week scope), the first action is a fresh milestone, not the Epic. Workflow:
215
- 1. `scripts/orboto.mjs post /projects/<id>/milestones '{"name":"<Feature name>","startDate":null,"endDate":null}'` (or the MCP `orboto_create_milestone` tool)
216
- 2. Create the Epic with `create-ticket --type epic ... --description-stdin` carrying the vision + phase overview.
217
- 3. `set-milestone <epicKey> "<Feature name>"`.
218
- 4. Create each phase ticket with `--parent <epicKey>`; immediately `set-milestone <phaseKey> "<Feature name>"` for each.
219
-
220
- The milestone collects the whole cluster so the burndown view, the milestone page, and any roadmap export pick it up as a single unit. Three small fixes or a single ticket do NOT need their own milestone - those go to `Feature Backlog` / `Bugs` / a thematic existing milestone. Materialisation rules from rule 7 still apply: only create the phase children when the parent is actively being worked; the milestone itself, the Epic, and any pre-committed phase tickets stay on disk as the roadmap signal.
221
-
222
- These rules are enforced reactively by orboto's drift detection (commits with no ticket key, time on the wrong ticket, work marked done that never shipped). To catch drift *before* it happens - at the prompt and at the commit - the operator can install optional harness-side guards (a Claude Code `UserPromptSubmit` hook + a global git `commit-msg` hook via `init.templateDir`). Setup recipe with copy-paste-ready snippets: `docs/agent-workflow-enforcement.md`.
223
-
224
- When the ticket system was unreachable during work (rare but it happens), ship anyway. Afterwards create one retro-ticket per logical task and mark it `done` directly with a pointer to the commit SHA - the board stays truthful, the burndown still balances.
225
-
226
- Long descriptions (> 1 KB or containing code fences) go through `--description-stdin`, not as inline-arg strings. Heredoc-piped input avoids shell-escape breakage and permission prompts.
227
-
228
- Walking through the full lifecycle start-to-finish lives in *Workflow A* below. The reference table in *Operation reference* is the catalogue when you know the shape you want but forgot the exact command.
229
-
230
- A short list of additional habits - Conventional Commits prefix, release-push discipline, two more wrapper rules, key-not-UUID output hygiene - lives in `CONVENTIONS.md` next to this file. Read it once after install; teams that disagree can override or remove it.
231
-
232
- ---
233
-
234
- ## Workflow A - Dev: pick up work and execute
235
-
236
- **Rule zero: every bug fix or feature gets a ticket first.** When the operator describes a change in chat (even casually - "fix X", "add Y"), create a ticket for it before you start coding. The ticket is what the operator and the rest of the team see; silent fixes in commits don't show up in the board, the burndown, or anyone's weekly review. The one exception is a tiny incremental fix to a ticket you're already working on - that stays on the same ticket.
237
-
238
- ```bash
239
- # Bug the operator just reported - create a ticket first, then claim it.
240
- scripts/orboto.mjs create-ticket ORB "Members picker breaks when user has no fullName" \
241
- --type=bug --priority=high --description-stdin <<'EOF'
242
- Clicking an invited guest in the picker throws "cannot read properties of undefined"
243
- because the picker assumes every row has `fullName`. Needs a null-safe fallback.
244
- EOF
245
- # Output includes id + ticketKey. Then:
246
- scripts/orboto.mjs claim ORB-42
247
- ```
248
-
249
- 1. **Find work**
250
- - `scripts/orboto.mjs my-tickets` - tickets already yours
251
- - `scripts/orboto.mjs list-tickets ORB --status=todo` - unclaimed in-scope
252
- - Sort by `priority` (blocker > high > normal > low > trivial), then `dueDate` ascending (nulls last), then `ticketNumber`
253
-
254
- 2. **Claim it** (one command)
255
- ```bash
256
- scripts/orboto.mjs claim ORB-42
257
- ```
258
- This self-assigns, moves the ticket to `in_progress`, and starts the timer atomically.
259
-
260
- 3. **Post a "taking this" comment** before you start coding - one short line so humans can see you've picked it up:
261
- ```bash
262
- scripts/orboto.mjs comment ORB-42 "Picked this up - starting on <rough plan>."
263
- ```
264
-
265
- 4. **Do the work.** If you hit a blocker or need clarification, comment instead of guessing:
266
- ```bash
267
- scripts/orboto.mjs comment ORB-42 "Question: <what you need>."
268
- ```
269
-
270
- 5. **Commit & push.** Reference the ticket in the commit message so the Git integration links activity.
271
-
272
- **Good commit headline format** - put the ticket key at the END of the subject line, in parentheses, next to the Conventional-Commits prefix:
273
- ```
274
- feat(auth): add token rotation (ORB-42)
275
- fix(checklists): align store + effective flags (ORB-234)
276
- ```
277
- Agents SHOULD always use the ticket key (not a bare hex short-id or UUID) - a plain sentence about "the token-rotation fix" without a key does not get linked, and the ticket's git-activity panel stays empty. Full parser reference (the two fallback reference forms, Do/Avoid examples, multi-ticket commits): `CONVENTIONS.md` → "Commits".
278
-
279
- 6. **Stop the timer** (converts to a `time_entry` automatically):
280
- ```bash
281
- scripts/orboto.mjs timer-stop "what you actually did"
282
- ```
283
-
284
- 7. **Summary comment + transition** - before moving the ticket, ALWAYS leave a short status note so a human reviewer can pick up cold without reading the diff. A good summary comment has: (a) what changed, one sentence; (b) the commit SHA; (c) how to verify in the UI. Example:
285
- ```
286
- Fix landed in 251d348. The members tab now has a search-first picker.
287
- To verify: Project Settings → Members should show a search input, not
288
- an email input.
289
- ```
290
- Then move the status:
291
- ```bash
292
- scripts/orboto.mjs move ORB-42 in_review # if a human should review
293
- scripts/orboto.mjs move ORB-42 done # only if self-merged and nobody else needs to look
294
- ```
295
-
296
- **Don't close if a linked PR is still open.** Check `scripts/orboto.mjs get /tickets/<id>/git-activity` - if `latestPrState` is `open`, leave the ticket in `in_review` and wait for human review.
297
-
298
- ---
299
-
300
- ## Workflow B - PM: plan and delegate
301
-
302
- 1. **Understand the project**: `GET /projects/:id` gives you members, milestones, labels, budget. Also pull `GET /projects/:id/ticket-statuses` so you know the workflow.
303
-
304
- 2. **Define a milestone** - use the shortcut so the nullable-but-required
305
- date keys get filled in automatically:
306
- ```bash
307
- scripts/orboto.mjs create-milestone ORB "Sprint 1" \
308
- --start 2026-05-01 --end 2026-05-14
309
- # Dates optional - omit both for an undated milestone.
310
- ```
311
- The underlying `POST /projects/:id/milestones` requires `startDate` and
312
- `endDate` to be present in the body **even when null** (Zod schema
313
- treats them as nullable-but-required). Hand-rolled
314
- `post /projects/:id/milestones '{"name":"X"}'` returns
315
- `400 body/startDate Required` - always prefer the shortcut.
316
-
317
- 3. **Break into tickets** using `create-ticket` - pass `--milestone` and `--assign` directly to avoid separate follow-up calls:
318
- ```bash
319
- scripts/orboto.mjs create-ticket ORB "Implement login rate limiting" \
320
- --type=task --priority=high \
321
- --milestone="Sprint 1" \
322
- --assign=alice@company.com \
323
- --description="Limit POST /auth/login to 10 req/min per IP using the existing rate-limit hook."
324
- # Returns: { id, ticketKey, title, status, url }
325
- ```
326
-
327
- Valid `--type` values: `task` / `bug` / `story` / `epic`
328
- Valid `--priority` values: `blocker` / `high` / `normal` / `low` / `trivial` (default `normal`)
329
-
330
- Only escalate to `blocker` / `high` when the ticket genuinely gates other work or has a hard deadline - spammy escalation is worse than none.
331
-
332
- 4. **Assign / reassign owners after creation**:
333
- ```bash
334
- scripts/orboto.mjs assign ORB-42 bob@company.com # resolves email → userId automatically
335
- scripts/orboto.mjs unassign ORB-42 alice@company.com
336
- scripts/orboto.mjs set-milestone ORB-42 "Sprint 2" # resolves name → UUID automatically
337
- ```
338
-
339
- 5. **Dependencies** - if A blocks B (i.e. B depends on A):
340
- ```bash
341
- scripts/orboto.mjs add-dependency ORB-B ORB-A # B depends on A
342
- scripts/orboto.mjs list-dependencies ORB-B
343
- scripts/orboto.mjs remove-dependency ORB-B ORB-A
344
- ```
345
- The wrappers resolve ticket keys → UUIDs and look up the project for you. Both tickets must live in the same project; cycles are rejected server-side.
346
-
347
- 6. **Monitor** - re-check periodically:
348
- - `scripts/orboto.mjs list-tickets ORB --status=in_progress` - what's active?
349
- - `scripts/orboto.mjs get /alert-events?resolved=false&projectId=<id>` - anything firing?
350
- - `scripts/orboto.mjs get /analytics/burndown?projectId=<id>&milestoneId=<id>` - on track?
351
- - `scripts/orboto.mjs get /capacity` - who is overbooked?
352
-
353
- ---
354
-
355
- ## OQL - quick cheatsheet
356
-
357
- `orboto query "<oql>"` is the typed query DSL for tickets, for when the per-entity shortcuts (`my-tickets`, `list-tickets`, `search`) cannot express the filter you need - combined `assignee + dueDate + label + statusCategory` with explicit ORDER BY, "all blocked tickets across every project", "tickets I touched this week", etc. **Full grammar, the complete field list, JQL mode, pagination, and `--explain` live in `ADVANCED.md`** - read it when you need a filter beyond these examples.
358
-
359
- **Shape:** `<field> <op> <value> [AND|OR|NOT ...] [ORDER BY ...] [LIMIT n]`. Empty input matches every row you can see (subject to ACL).
360
- **Operators:** `=` `!=` `<` `<=` `>` `>=` `~` (contains, ILIKE) `!~` `IN (a,b,c)` `IS [NOT] NULL` `IS [NOT] EMPTY`.
361
- **Functions:** `currentUser()` `now()` `startOfWeek()` `endOfWeek()` `startOfMonth()` `endOfMonth()` `daysAgo(n)`.
362
-
363
- ```
364
- # My open tickets across every project, blockers first
365
- query "assignee = currentUser() AND statusCategory != done ORDER BY priority DESC, dueDate ASC"
366
-
367
- # Anything blocking ORB this week
368
- query "project = ORB AND priority IN (blocker, high) AND dueDate <= endOfWeek()"
369
-
370
- # Tickets I touched in the last 7 days
371
- query "assignee = currentUser() AND updatedAt >= daysAgo(7) ORDER BY updatedAt DESC"
372
-
373
- # Estimated more than logged -> at-risk for budget
374
- query "loggedMinutes > estimatedTimeMinutes AND statusCategory != done"
375
- ```
376
-
377
- ---
378
-
379
- ## Keeping the skill in sync with the API
380
-
381
- The skill version follows the API version - every release of orboto ships a matching skill bundle that the API serves at `GET /skills/orboto/manifest`. When the local skill drifts from the running API (different routes, missing shortcuts, schema changes), `orboto self-update` fetches the matching bundle:
382
-
383
- - Compares the local `manifest.json` (at the skill root) against `/version`.
384
- - Pulls each file whose SHA256 differs (or `--force` re-downloads everything).
385
- - Verifies SHA256 post-download; aborts if any check fails so a half-corrupted skill is never written.
386
- - Writes a fresh `manifest.json` recording the new version.
387
-
388
- The skill files are part of the user's repo. After `self-update`, the working tree is dirty - running `git diff` on the skill directory shows the change. The user can commit it, leave it as a local override, or `git restore` it back. The script itself does not commit.
389
-
390
- If `manifest` returns 503 ("Skill source is not bundled with this API instance"), the operator needs to rebuild the API image with `skills/orboto/` included or set `ORBOTO_SKILL_PATH`. Both the manifest and download endpoints share the same source directory.
391
-
392
- ---
393
-
394
- ## Checklists on tickets
395
-
396
- Checklists are the right primitive for **checkable steps on the same ticket where a sub-ticket would be overhead**: acceptance criteria, test plans, reminders. Two or three lines of text each.
397
-
398
- When NOT a checklist:
399
- - Independent work with its own owner / due date → **sub-ticket** (`create-ticket --parent`). Checklist items don't have assignees on purpose.
400
- - A question → **comment**. Comments capture discussion, checklists capture progress.
401
- - A reference to another ticket → checklist item with `--link ORB-ID`. The item's completion will track that ticket's status.
402
-
403
- ### Duplicate-suggestion rule
404
-
405
- `add-check` may return suggestions - existing tickets in the same project whose text matches the item content. Agents MUST surface these to the operator before committing. Silent-ignore is a foul. The wrapper prints the suggestion list as part of the tool output; the response to the operator should ask whether to promote the just-created item to a `linkedTicketId` reference or keep it as free text.
406
-
407
- ### Completion hygiene
408
-
409
- When shipping work that resolves a checklist item, tick **every** applicable item, not just the most obvious. A human reading the ticket after the fact uses the 3/5 checklist to gauge completeness - a half-ticked list makes finished work look half-done. If the item text no longer matches what got built, edit it first, then check.
410
-
411
- ### Gating (`triggers_done`)
412
-
413
- Lists flagged `triggers_done` (set via `new-checklist --triggers-done` or the UI menu) gate the auto-done transition: the ticket stays open until every item in every gating list is complete. Non-gating lists (reminders, nice-to-haves) don't block.
414
-
415
- ---
416
-
417
- ## Companion files - read on demand
418
-
419
- This SKILL.md is the entrypoint: it holds what every session needs - setup, the wrapper-shortcut summary, the rhythm, the duplicate-check procedure, Workflow A/B, and the safety rules. Reference material an agent only needs for a specific task lives in companion files next to this one (same install directory, same downloadable bundle - `self-update` keeps them in sync via per-file SHA256). Read the matching file when the task calls for it:
420
-
421
- - **`REFERENCE.md`** - the full operation-reference catalogue (~110 rows: every non-doc shortcut + raw call, with per-op notes), pagination mechanics, and the AI-dependent-operations matrix. Read it when you know the shape you want but forgot the exact command or flag.
422
- - **`DOCS-WIKI.md`** - doc spaces, docs, revisions, comments, attachments, exports, the wiki-* curation commands, and URL/file ingest. Read it when the task touches the docs/wiki surface.
423
- - **`ADVANCED.md`** - daemon-mode (high-volume speedup), multi-agent coordination, the OQL deep-dive (full grammar + field list + JQL), the project-primer maintenance guide, and personal AI preferences. Read it for those specific workflows.
424
- - **`CONVENTIONS.md`** - commit + release etiquette and a few wrapper habits. Read it once after install.
425
-
426
- ---
427
-
428
- ## Safety rules (follow even if not asked)
429
-
430
- - **Every bug fix or feature gets a ticket first.** When the operator describes work in chat, create the ticket via `create-ticket` *before* you start implementing. The board - not the chat log - is the team's record of what's happening. The only exception: incremental tweaks to a ticket you're already working on stay on that ticket.
431
- - **Leave a trail.** Every state change on a ticket - claiming, asking a question, blocking on review, closing - MUST be accompanied by a short comment so humans can catch up without reading your diff. One short paragraph is enough. Silent work is a bug: a human picking up a ticket you've been moving around shouldn't have to read tool logs to understand what happened.
432
- - **No destructive ops without explicit, current-turn consent.** Never call `DELETE` on tickets, milestones, users, projects, comments, attachments, docs, webhooks, API keys unless the operator says so **in this turn**.
433
- - **Stay out of `/admin/*`** unless explicitly authorized, even if the bot account has `admin:*` permissions.
434
- - **Never log or echo** `ORBOTO_TOKEN`, API keys, session tokens, passwords, or `Authorization` headers. Redact in any diagnostic output you produce.
435
- - **Don't spam.** One comment per meaningful state change - not per minor finding. Group multiple findings into a single update rather than one comment per line of investigation.
436
- - **Respect internal comments.** `isInternal: true` hides the comment from external/guest users - use it when discussing implementation details the customer shouldn't see.
437
- - **Rate limits.** Global cap is 600/min; `/auth/*`, `/system/setup`, `/invitations/*`, `/admin/users/*` are at 20/min. Back off on HTTP 429.
438
- - **Close with a summary** that names the commit SHA, explains what was done in one sentence, and tells the reviewer how to verify in the UI. Never close silently.
439
- - **No impersonation.** `POST /auth/login` with someone else's credentials is never the right move, even if you could somehow obtain them.
440
-
441
- ---
442
-
443
- ## Working alongside humans
444
-
445
- You are a teammate, not a runaway bot.
446
-
447
- - On long-running tickets, comment status updates when state changes ("tests passing, waiting for review", "implementation paused - design sign-off pending") - not as a keepalive.
448
- - **Blockers go through the dependency primitive, not a comment.** If you find that ticket A can't move until ticket B lands, run `scripts/orboto.mjs add-dependency ORB-A ORB-B` - that's what surfaces the relationship in the dependency panel, the Gantt arrows, the "what's blocking my work" OQL filter (`blockedBy IS NOT EMPTY`), and milestone-risk reporting. A comment like "blocked by ORB-B" is invisible to every one of those surfaces. **A and B may be in different projects** (ORB-1614) - do NOT invent a same-project proxy ticket to stand in for the real cross-project blocker; that's exactly the management overhead this feature removed. Write the dependency, then comment one short line explaining what changed.
449
- - If a decision requires product, security, or legal judgement, **assign a human** and leave a comment explaining what you need.
450
- - After merge/PR activity the Git integration posts its own comment - don't duplicate that information.
451
- - If a ticket has an open linked PR (`latestPrState === 'open'`), leave the status in `in_review`; don't close.
452
- - Use `@name` in comments to ping people on decisions they own (the notification trigger is being wired up; even without it the mention stays visible).
453
-
454
-
455
- ---
456
-
457
- ## Portability
458
-
459
- This skill follows the [Agent Skills specification](https://agentskills.io/specification.md):
460
-
461
- ```
462
- orboto/
463
- ├── SKILL.md # this file - the entrypoint every session reads
464
- ├── REFERENCE.md # full operation-reference catalogue (read on demand)
465
- ├── DOCS-WIKI.md # docs / wiki / ingest operations (read on demand)
466
- ├── ADVANCED.md # daemon-mode, multi-agent, OQL deep-dive, primer + personal facts
467
- ├── CONVENTIONS.md # commit + release etiquette
468
- ├── .env # gitignored - ORBOTO_BASE_URL + ORBOTO_TOKEN for local use
469
- └── scripts/
470
- └── orboto.mjs # Node wrapper (native fetch, no deps, auto-loads .env)
471
- ```
472
-
473
- Any agent runtime that honours the spec - Claude Code, Claude API, Copilot, GPT-family harnesses, local agent stacks - can install this directory and use it as-is. Non-Claude runtimes that don't parse Agent Skills can still feed `SKILL.md` into a system prompt and invoke `scripts/orboto.mjs` as a subprocess.
474
-
475
- The wrapper needs only Node.js 18+ (native `fetch`) and two env vars (`ORBOTO_BASE_URL`, `ORBOTO_TOKEN`). No package install, no build step.
476
-