@awebai/oats 0.29.4 → 0.30.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/README.md +12 -6
- package/bin/oats.mjs +194 -50
- package/docs/capabilities.md +160 -171
- package/docs/capability-manifest.schema.json +6 -11
- package/docs/configuration.md +213 -64
- package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
- package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
- package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
- package/docs/design/2026-09-27-team-model-v2.md +97 -117
- package/docs/design/2026-09-28-automations-trust.md +38 -0
- package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
- package/docs/design/HISTORY.md +65 -0
- package/docs/design/README.md +23 -54
- package/docs/desktop-cli-api.md +1787 -1777
- package/docs/desktop.md +30 -91
- package/docs/execution-targets.md +146 -292
- package/docs/first-team.md +31 -17
- package/docs/implementation.md +77 -288
- package/docs/integrations.md +118 -320
- package/docs/knowledge-capability-authoring.md +25 -52
- package/docs/knowledge-reference/acceptance.md +3 -3
- package/docs/knowledge-reference/adoption.md +1 -1
- package/docs/knowledge-reference/harvester.md +2 -2
- package/docs/knowledge-reference/package-craft.md +3 -3
- package/docs/knowledge-reference/provider-mapping.md +3 -6
- package/docs/knowledge-reference/reader-capture.md +3 -3
- package/docs/knowledge-theory.md +62 -166
- package/docs/knowledge.md +225 -404
- package/docs/layers.md +42 -97
- package/docs/oats-local.schema.json +58 -5
- package/docs/oats-membership.schema.json +1 -8
- package/docs/oats-package.schema.json +5 -5
- package/docs/oats-workspace.schema.json +8 -22
- package/docs/official-catalog.md +25 -28
- package/docs/packages.md +45 -63
- package/docs/plans/0.30-close-out.md +83 -0
- package/docs/release-lane.md +82 -0
- package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
- package/docs/release-notes/v0.19.0.md +48 -147
- package/docs/release-notes/v0.19.1.md +2 -3
- package/docs/release-notes/v0.19.3.md +2 -15
- package/docs/release-notes/v0.20.0.md +0 -15
- package/docs/release-notes/v0.22.0.md +71 -138
- package/docs/release-notes/v0.22.1.md +42 -90
- package/docs/release-notes/v0.22.10.md +1 -1
- package/docs/release-notes/v0.22.11.md +1 -47
- package/docs/release-notes/v0.22.12.md +4 -13
- package/docs/release-notes/v0.22.13.md +1 -42
- package/docs/release-notes/v0.22.14.md +3 -11
- package/docs/release-notes/v0.22.15.md +1 -46
- package/docs/release-notes/v0.22.16.md +6 -8
- package/docs/release-notes/v0.22.18.md +1 -99
- package/docs/release-notes/v0.22.19.md +3 -14
- package/docs/release-notes/v0.22.2.md +6 -15
- package/docs/release-notes/v0.22.3.md +0 -1
- package/docs/release-notes/v0.22.4.md +1 -14
- package/docs/release-notes/v0.22.5.md +2 -12
- package/docs/release-notes/v0.22.6.md +0 -3
- package/docs/release-notes/v0.23.0.md +9 -25
- package/docs/release-notes/v0.23.1.md +9 -25
- package/docs/release-notes/v0.23.2.md +2 -4
- package/docs/release-notes/v0.24.0.md +56 -97
- package/docs/release-notes/v0.24.1.md +7 -11
- package/docs/release-notes/v0.24.10.md +34 -45
- package/docs/release-notes/v0.24.11.md +12 -20
- package/docs/release-notes/v0.24.12.md +35 -48
- package/docs/release-notes/v0.24.13.md +34 -41
- package/docs/release-notes/v0.24.2.md +9 -13
- package/docs/release-notes/v0.24.3.md +7 -11
- package/docs/release-notes/v0.24.4.md +6 -6
- package/docs/release-notes/v0.24.5.md +6 -10
- package/docs/release-notes/v0.24.6.md +2 -5
- package/docs/release-notes/v0.24.7.md +46 -75
- package/docs/release-notes/v0.24.8.md +58 -96
- package/docs/release-notes/v0.24.9.md +38 -54
- package/docs/release-notes/v0.25.0.md +59 -76
- package/docs/release-notes/v0.25.1.md +57 -81
- package/docs/release-notes/v0.25.2.md +51 -70
- package/docs/release-notes/v0.25.3.md +11 -13
- package/docs/release-notes/v0.25.4.md +9 -13
- package/docs/release-notes/v0.25.5.md +3 -5
- package/docs/release-notes/v0.25.6.md +20 -29
- package/docs/release-notes/v0.25.7.md +5 -7
- package/docs/release-notes/v0.25.8.md +26 -39
- package/docs/release-notes/v0.26.0.md +175 -646
- package/docs/release-notes/v0.27.0.md +4 -5
- package/docs/release-notes/v0.27.1.md +4 -6
- package/docs/release-notes/v0.27.2.md +1 -1
- package/docs/release-notes/v0.28.0.md +57 -124
- package/docs/release-notes/v0.29.0.md +89 -208
- package/docs/release-notes/v0.29.1.md +1 -1
- package/docs/release-notes/v0.29.2.md +3 -4
- package/docs/release-notes/v0.30.0.md +205 -0
- package/docs/release-notes/v0.30.1.md +123 -0
- package/docs/schedules.md +280 -363
- package/docs/servers.md +99 -117
- package/docs/soul.schema.json +2 -9
- package/docs/souls-and-instances.md +145 -158
- package/docs/workspaces.md +137 -215
- package/lib/automations.mjs +21 -6
- package/lib/core.mjs +226 -74
- package/lib/instance-events.mjs +1 -1
- package/lib/instance-inspect.mjs +109 -34
- package/lib/instance-lifecycle.mjs +14 -1
- package/lib/instance-resolution.mjs +26 -27
- package/lib/launch-preference.mjs +87 -0
- package/lib/materialize.mjs +3 -3
- package/lib/packages.mjs +1 -1
- package/lib/resolve.mjs +30 -88
- package/lib/schedule.mjs +1 -1
- package/lib/teams-verbs.mjs +195 -0
- package/lib/teams.mjs +190 -0
- package/lib/triggers.mjs +2 -2
- package/lib/workspace.mjs +54 -147
- package/package-catalog.json +10 -16
- package/package.json +1 -3
- package/skills/oats-getting-started/SKILL.md +25 -13
- package/capabilities/oats-authoring/LICENSE +0 -21
- package/capabilities/oats-authoring/oats-package.json +0 -11
- package/capabilities/oats-authoring/oats.json +0 -12
- package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +0 -84
- package/capabilities/oats-authoring/skills/skill-craft/SKILL.md +0 -109
- package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +0 -116
- package/capabilities/oats-aweb/bin/oats-aweb-binding.mjs +0 -11
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +0 -1338
- package/capabilities/oats-aweb/injects/aweb.md +0 -47
- package/capabilities/oats-aweb/lib/binding-wire.mjs +0 -356
- package/capabilities/oats-aweb/lib/captured-execution.mjs +0 -91
- package/capabilities/oats-aweb/lib/captured-native.mjs +0 -91
- package/capabilities/oats-aweb/lib/grant-custody.mjs +0 -38
- package/capabilities/oats-aweb/lib/invocation-shape.mjs +0 -135
- package/capabilities/oats-aweb/lib/portable-binding.mjs +0 -146
- package/capabilities/oats-aweb/lib/session-readiness.mjs +0 -56
- package/capabilities/oats-aweb/lib/wake-receive.mjs +0 -56
- package/capabilities/oats-aweb/oats.json +0 -208
- package/capabilities/oats-aweb/skills/LICENSE +0 -21
- package/capabilities/oats-aweb/skills/VENDORED.md +0 -31
- package/capabilities/oats-aweb/skills/aweb-identity/SKILL.md +0 -201
- package/capabilities/oats-aweb/skills/aweb-messaging/SKILL.md +0 -161
- package/capabilities/oats-aweb/skills/aweb-messaging/references/messaging-scenarios.md +0 -61
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +0 -116
- package/capabilities/oats-aweb/skills/aweb-team-membership/references/team-membership-reference.md +0 -74
- package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +0 -216
- package/capabilities/oats-jira/bin/oats-jira.mjs +0 -40
- package/capabilities/oats-jira/injects/jira.md +0 -10
- package/capabilities/oats-jira/oats.json +0 -22
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +0 -179
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +0 -34
- package/capabilities/oats-linear/bin/oats-linear.mjs +0 -344
- package/capabilities/oats-linear/injects/linear.md +0 -8
- package/capabilities/oats-linear/oats.json +0 -24
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +0 -223
- package/capabilities/oats-okf/bin/oats-okf-binding.mjs +0 -14
- package/capabilities/oats-okf/bin/oats-okf.mjs +0 -209
- package/capabilities/oats-okf/injects/okf.md +0 -42
- package/capabilities/oats-okf/lib/binding-wire.mjs +0 -348
- package/capabilities/oats-okf/lib/captured-worker.mjs +0 -109
- package/capabilities/oats-okf/lib/config.mjs +0 -124
- package/capabilities/oats-okf/lib/consult.mjs +0 -518
- package/capabilities/oats-okf/lib/harvest-status.mjs +0 -88
- package/capabilities/oats-okf/lib/harvest-switch.mjs +0 -94
- package/capabilities/oats-okf/lib/inspection.mjs +0 -119
- package/capabilities/oats-okf/lib/invocation-context.mjs +0 -111
- package/capabilities/oats-okf/lib/invocation-shape.mjs +0 -135
- package/capabilities/oats-okf/lib/io.mjs +0 -118
- package/capabilities/oats-okf/lib/migration.mjs +0 -137
- package/capabilities/oats-okf/lib/okf-validate.mjs +0 -123
- package/capabilities/oats-okf/lib/portable-binding.mjs +0 -199
- package/capabilities/oats-okf/lib/source-contract.mjs +0 -46
- package/capabilities/oats-okf/lib/sources.mjs +0 -424
- package/capabilities/oats-okf/lib/stores.mjs +0 -473
- package/capabilities/oats-okf/lib/worker.mjs +0 -497
- package/capabilities/oats-okf/oats.json +0 -148
- package/capabilities/oats-okf/schemas/okf-base.schema.json +0 -46
- package/capabilities/oats-okf/schemas/okf-bindings.schema.json +0 -112
- package/capabilities/oats-okf/schemas/okf-portable-declaration.schema.json +0 -87
- package/capabilities/oats-okf/schemas/okf-portable-payload.schema.json +0 -113
- package/capabilities/oats-okf/schemas/okf-soul.schema.json +0 -37
- package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +0 -144
- package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +0 -86
- package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +0 -104
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +0 -140
- package/capabilities/oats-okf-harvest/injects/harvester.md +0 -12
- package/capabilities/oats-okf-harvest/oats.json +0 -26
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +0 -168
- package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +0 -192
- package/capabilities/oats-okf-harvest/skills/okf-authoring/SKILL.md +0 -151
- package/capabilities/oats-okf-harvest/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +0 -170
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +0 -12
- package/capabilities/oats-okf-maintenance/lib/provenance.mjs +0 -50
- package/capabilities/oats-okf-maintenance/oats.json +0 -21
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +0 -159
- package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +0 -192
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +0 -151
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +0 -146
- package/capabilities/oats-review/injects/review.md +0 -69
- package/capabilities/oats-review/oats.json +0 -10
- package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
- package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
- package/docs/conventions.md +0 -90
- package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
- package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
- package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
- package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
- package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
- package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
- package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
- package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
- package/docs/design/2026-09-15-captured-dispatch.md +0 -127
- package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
- package/docs/design/2026-09-15-package-preparation.md +0 -100
- package/docs/design/2026-09-15-portable-data-contract.md +0 -121
- package/docs/design/2026-09-15-portable-declarations.md +0 -189
- package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
- package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
- package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
- package/docs/design/2026-09-15-source-observation.md +0 -119
- package/docs/design/2026-09-16-captured-admission.md +0 -77
- package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
- package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
- package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
- package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
- package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
- package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
- package/docs/design/2026-09-16-portable-onboarding.md +0 -179
- package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
- package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
- package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
- package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
- package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
- package/docs/design/2026-09-17-captured-native-start.md +0 -58
- package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
- package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
- package/docs/design/2026-09-17-public-captured-start.md +0 -108
- package/docs/design/2026-09-17-public-prepare-request.md +0 -90
- package/docs/design/2026-09-18-captured-pi-host.md +0 -205
- package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
- package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
- package/docs/design/2026-09-20-redesign-program-board.md +0 -142
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
- package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
- package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
- package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
- package/docs/design/2026-09-24-phase-d-plan.md +0 -305
- package/docs/design/2026-09-25-teams-contract.md +0 -258
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
- package/docs/design/desktop-ux-plan.md +0 -362
- package/docs/design/launch-configurations.md +0 -168
- package/docs/design/okf-mirror-provenance.md +0 -105
- package/docs/design/operations-contract.md +0 -141
- package/docs/oats-member.schema.json +0 -38
- package/skills/integration-authoring/SKILL.md +0 -84
- package/skills/oats-support/SKILL.md +0 -79
- package/skills/skill-craft/SKILL.md +0 -109
- package/skills/soul-craft/SKILL.md +0 -116
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: jira-tasks
|
|
3
|
-
description: >-
|
|
4
|
-
Jira task tracking and agent roster protocol for OATS agents. Use when you
|
|
5
|
-
are an agent instance working an epic, story, or task in Jira: reading your
|
|
6
|
-
assignment, finding your work queue, joining or leaving an epic's Agent
|
|
7
|
-
Roster, posting progress or handoff comments, transitioning ticket status,
|
|
8
|
-
or creating stories/tasks under an epic. Also use when asked about "the
|
|
9
|
-
board", "the roster", "your ticket", "epic status", or task tracking
|
|
10
|
-
between agents. Uses the acli CLI (Atlassian CLI) from bash.
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Agent task tracking (Jira)
|
|
14
|
-
|
|
15
|
-
Jira is your deployment's **tasks layer** — the shared record for task
|
|
16
|
-
tracking and the agent roster. Agents do not know about projects — you know
|
|
17
|
-
**repos and epics**. The hierarchy:
|
|
18
|
-
|
|
19
|
-
- **Epic** — the unit of work that kicks off, runs, and completes. May touch
|
|
20
|
-
one repo or several. Its description carries the **Agent Roster** — the
|
|
21
|
-
source of truth for who is on the epic and their role. Bugs/support run as
|
|
22
|
-
a standing epic.
|
|
23
|
-
- **Story** — a **group of related tasks** covering one part of the epic's
|
|
24
|
-
work (often one repo's slice, one feature area). Not every task needs a
|
|
25
|
-
story.
|
|
26
|
-
- **Task** — a small bounded item, the thing an agent actually works. Lives
|
|
27
|
-
**either directly under the epic** (standalone item) **or under a story**
|
|
28
|
-
(part of a grouped slice). Everything traces up to an epic.
|
|
29
|
-
|
|
30
|
-
## Site and project (from your deployment, never hardcoded)
|
|
31
|
-
|
|
32
|
-
Your Jira **site** and **project key** come from the tasks payload OATS merged
|
|
33
|
-
for this instance: the soul's `soul.yaml` `tasks: { site, project }`, the
|
|
34
|
-
deployment's `oats-local.yaml` `settings.oats.jira.{site,project}`, or a
|
|
35
|
-
spawn's `--provider oats.jira key=value`. Find them, in order:
|
|
36
|
-
|
|
37
|
-
1. Your `TASK.md` briefing — the spawn hook writes a
|
|
38
|
-
`Tasks: Jira — project <KEY> on <site>` line.
|
|
39
|
-
2. `./instance.json` in your instance home — `providers["oats.jira"]` is the
|
|
40
|
-
merged payload this instance received.
|
|
41
|
-
3. Ask your human.
|
|
42
|
-
|
|
43
|
-
Below, `<PROJECT>` means that project key. If site or project are unset,
|
|
44
|
-
STOP and ask your human to set them — do not guess.
|
|
45
|
-
|
|
46
|
-
**First use**: run `acli jira auth status` — if unauthorized, STOP and tell
|
|
47
|
-
the human to run `acli jira auth login --web`. Never attempt login yourself.
|
|
48
|
-
|
|
49
|
-
## Identity rules (non-negotiable)
|
|
50
|
-
|
|
51
|
-
- **The human assignee is always the owning engineer** (never change
|
|
52
|
-
assignee to yourself; agents are not Jira users). Do not touch assignee
|
|
53
|
-
unless told.
|
|
54
|
-
- **You are identified by label and description**, not the assignee field:
|
|
55
|
-
- Label `agent-<your-instance-alias>` on any story/task you work.
|
|
56
|
-
- An `Agent:` line in the description (see templates).
|
|
57
|
-
- **Never set or modify sprints.** Never delete tickets. Comment, don't
|
|
58
|
-
rewrite, other agents' descriptions (exception: coordinators maintain the
|
|
59
|
-
roster table).
|
|
60
|
-
|
|
61
|
-
## Your work queue
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
acli jira workitem search --jql "project = <PROJECT> AND labels = agent-<alias> AND statusCategory != Done ORDER BY rank" --json
|
|
65
|
-
acli jira workitem view <PROJECT>-1234 --json # read one ticket (description, labels, status)
|
|
66
|
-
acli jira workitem search --jql "project = <PROJECT> AND parent = <PROJECT>-<epic> AND statusCategory != Done" --json # epic's direct children (stories + standalone tasks)
|
|
67
|
-
acli jira workitem search --jql "project = <PROJECT> AND parent = <PROJECT>-<story> AND statusCategory != Done" --json # a story's tasks
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
An epic's full open work = its direct children **plus** the tasks under each
|
|
71
|
-
of its stories — walk one level down from stories when you need the complete
|
|
72
|
-
picture.
|
|
73
|
-
|
|
74
|
-
Record your epic and ticket keys in your instance memory (e.g. `STATE.md`
|
|
75
|
-
`# Context`).
|
|
76
|
-
|
|
77
|
-
## The Agent Roster (epics)
|
|
78
|
-
|
|
79
|
-
The epic description contains a `## Agent Roster` markdown table — current
|
|
80
|
-
truth for who is on the epic:
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
## Agent Roster
|
|
84
|
-
|
|
85
|
-
| Agent (instance) | Soul / class | Repo | Role on epic | Status | Since |
|
|
86
|
-
|---|---|---|---|---|---|
|
|
87
|
-
| coordinator-digest | coordinator (newsletter) | newsletter-service | runs the epic | active | 2026-07-07 |
|
|
88
|
-
| developer-digest-api | developer (newsletter) | newsletter-service | implements API | active | 2026-07-07 |
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Protocol:
|
|
92
|
-
- **Joining**: the coordinator (or the spawning agent) adds your row to the
|
|
93
|
-
table (`acli jira workitem edit <epic> --description ...` with the full
|
|
94
|
-
updated description — read it first, edit only the roster table) AND posts
|
|
95
|
-
a comment: `[roster] <alias> joined — role: <role>, repo: <repo>`.
|
|
96
|
-
- **Leaving/retiring**: set the row's Status to `retired` (keep the row — it
|
|
97
|
-
is history) and comment `[roster] <alias> retired — <one-line outcome>`.
|
|
98
|
-
- Only edit the roster table; never rewrite the rest of the epic description.
|
|
99
|
-
- Comments are the event log; the table is current state. On conflict, fix
|
|
100
|
-
the table and note it in a comment.
|
|
101
|
-
|
|
102
|
-
## Working a ticket
|
|
103
|
-
|
|
104
|
-
1. Read your ticket and its epic (description + roster) before starting.
|
|
105
|
-
2. **Milestones** → comment on your ticket, prefixed `[<alias>]`. Mirror the
|
|
106
|
-
entry you record in your instance memory — same events, two audiences.
|
|
107
|
-
3. **Status transitions** — move your ticket as you work:
|
|
108
|
-
`acli jira workitem transition <PROJECT>-1234 --status "In Progress"`.
|
|
109
|
-
Discover valid statuses with `--help` or by trying; if a transition is
|
|
110
|
-
rejected, comment instead and let the coordinator move it.
|
|
111
|
-
4. **Done** = your latest commit is review-clean and the branch is handed
|
|
112
|
-
off. Comment the outcome (branch, PR link, verification), then transition.
|
|
113
|
-
5. **Handoff/blocked** → comment
|
|
114
|
-
`[<alias>] handoff → <next-alias>: <what+where>` or
|
|
115
|
-
`[<alias>] blocked: <what is needed, from whom>`.
|
|
116
|
-
|
|
117
|
-
Tasks ≠ messaging: status and outcomes live here in Jira; conversation lives
|
|
118
|
-
in your deployment's messaging layer. Mail nudges; Jira records.
|
|
119
|
-
|
|
120
|
-
## Creating tickets (coordinators; developers file follow-ups as Tasks)
|
|
121
|
-
|
|
122
|
-
House rules: summary ≤ 12 words, describe the requirement not the solution,
|
|
123
|
-
bugs always include reproduction steps, keep descriptions to a few bullets.
|
|
124
|
-
|
|
125
|
-
**Choosing the level:**
|
|
126
|
-
- Small bounded item, no siblings needed → **Task directly under the epic**.
|
|
127
|
-
- A part of the epic's work that breaks into several related tasks → **Story
|
|
128
|
-
under the epic, tasks under the story**. The story is the group, not the
|
|
129
|
-
work item — agents are assigned to its tasks (a story worked wholly by one
|
|
130
|
-
agent may carry that agent's label too).
|
|
131
|
-
- Never create a story for a single task, and never nest stories.
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
acli jira workitem create --project <PROJECT> --type Task --summary "<summary>" \
|
|
135
|
-
--parent <PROJECT>-<epic-or-story> --label "agent-<alias>" --description "<see template>"
|
|
136
|
-
acli jira workitem create --project <PROJECT> --type Story --summary "<summary>" \
|
|
137
|
-
--parent <PROJECT>-<epic> --description "<see template>"
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Story/Task description template
|
|
141
|
-
|
|
142
|
-
```
|
|
143
|
-
<What & why — 2-4 bullets. Acceptance criteria as a checklist.>
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
Agent: <instance-alias> (who works this — matches the agent-<alias> label; stories list it only when one agent works the whole story)
|
|
147
|
-
Soul: <soul-name> · Repo: <repo>
|
|
148
|
-
Parent: <PROJECT>-<epic-or-story-key> · Epic: <PROJECT>-<epic-key>
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Epic description template
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
<Intent — what this epic delivers and why. Walls — what is explicitly out.>
|
|
155
|
-
|
|
156
|
-
Repos touched: <repo>, <repo>
|
|
157
|
-
Human gates: <security/authz/migration/contract items needing sign-off, or "none">
|
|
158
|
-
|
|
159
|
-
## Agent Roster
|
|
160
|
-
|
|
161
|
-
| Agent (instance) | Soul / class | Repo | Role on epic | Status | Since |
|
|
162
|
-
|---|---|---|---|---|---|
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Comment conventions (machine-greppable prefixes)
|
|
166
|
-
|
|
167
|
-
- `[roster] <alias> joined|retired — …`
|
|
168
|
-
- `[<alias>] milestone: …` · `[<alias>] handoff → <alias>: …` ·
|
|
169
|
-
`[<alias>] blocked: …` · `[<alias>] done: branch <name>, <verification>`
|
|
170
|
-
|
|
171
|
-
## Verify-before-trusting
|
|
172
|
-
|
|
173
|
-
Jira workflows differ per site. On first real use in a deployment: check the
|
|
174
|
-
project's issue types (`Epic/Story/Task/Bug`), whether `--parent` links
|
|
175
|
-
stories/tasks to epics, whether a **Task can take a Story as parent** (some
|
|
176
|
-
Jira configs only allow that via the Sub-task type — if so, use Sub-tasks
|
|
177
|
-
under stories and treat them as tasks), and the exact status names. If
|
|
178
|
-
reality differs, note it in a comment on your ticket and tell your
|
|
179
|
-
coordinator so your deployment's conventions get recorded.
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/** OATS spawn briefing for the Linear tasks integration. Makes no API calls. */
|
|
3
|
-
const output = (value) => {
|
|
4
|
-
process.stdout.write(JSON.stringify(value) + "\n");
|
|
5
|
-
process.exit(0);
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const event = process.env.OATS_EVENT || process.argv[2];
|
|
9
|
-
if (event !== "spawn") output({ warning: `oats-linear: unknown event "${event}" (expected spawn)` });
|
|
10
|
-
|
|
11
|
-
let settings = {};
|
|
12
|
-
try { settings = JSON.parse(process.env.OATS_SETTINGS || "{}"); }
|
|
13
|
-
catch { output({ warning: "oats-linear: the tasks settings payload (OATS_SETTINGS) is not valid JSON" }); }
|
|
14
|
-
|
|
15
|
-
const instance = process.env.OATS_INSTANCE || "unknown-instance";
|
|
16
|
-
const team = settings.team;
|
|
17
|
-
const project = settings.project;
|
|
18
|
-
const label = `agent-${instance}`;
|
|
19
|
-
// Where team lives under the workspace model (0.26): the soul's tasks payload or this machine's settings.
|
|
20
|
-
const TEAM_HOMES = "tasks: { team } in the soul's soul.yaml, or settings.oats.linear.team in the deployment's oats-local.yaml";
|
|
21
|
-
const target = team
|
|
22
|
-
? `team ${team}${project ? `, default project ${project}` : ""}`
|
|
23
|
-
: `team unset — ask your human to set ${TEAM_HOMES}`;
|
|
24
|
-
const warnings = [];
|
|
25
|
-
if (!team) warnings.push(`settings.team is unset (set ${TEAM_HOMES})`);
|
|
26
|
-
if (!process.env.LINEAR_API_KEY) warnings.push("LINEAR_API_KEY is not in the spawn environment");
|
|
27
|
-
|
|
28
|
-
output({
|
|
29
|
-
meta: { label, ...(team ? { team } : {}), ...(project ? { project } : {}) },
|
|
30
|
-
brief: `Tasks: Linear — ${target}. Your agent identity is label "${label}"; keep the human assignee unchanged. Load the linear-tasks skill before touching issues.`,
|
|
31
|
-
...(warnings.length ? {
|
|
32
|
-
warning: `oats-linear: ${warnings.join("; ")} — see the linear-tasks skill`,
|
|
33
|
-
} : {}),
|
|
34
|
-
});
|
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* JSON-first Linear task operations for OATS.
|
|
4
|
-
*
|
|
5
|
-
* Uses Linear's official GraphQL API directly. No third-party Linear CLI or
|
|
6
|
-
* SDK is required; authentication is a personal key in LINEAR_API_KEY.
|
|
7
|
-
*/
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
|
|
10
|
-
const API_URL = process.env.LINEAR_API_URL || "https://api.linear.app/graphql";
|
|
11
|
-
const argv = process.argv.slice(2);
|
|
12
|
-
const command = argv.shift();
|
|
13
|
-
|
|
14
|
-
function die(message, details) {
|
|
15
|
-
process.stderr.write(JSON.stringify({ error: String(message), ...(details ? { details } : {}) }, null, 2) + "\n");
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
function print(value) { process.stdout.write(JSON.stringify(value, null, 2) + "\n"); }
|
|
19
|
-
function parseArgs(values) {
|
|
20
|
-
const options = new Map();
|
|
21
|
-
const positional = [];
|
|
22
|
-
for (let i = 0; i < values.length; i++) {
|
|
23
|
-
const value = values[i];
|
|
24
|
-
if (!value.startsWith("--")) { positional.push(value); continue; }
|
|
25
|
-
const name = value.slice(2);
|
|
26
|
-
const next = values[i + 1];
|
|
27
|
-
const parsed = next !== undefined && !next.startsWith("--") ? values[++i] : true;
|
|
28
|
-
const previous = options.get(name) || [];
|
|
29
|
-
previous.push(parsed);
|
|
30
|
-
options.set(name, previous);
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
positional,
|
|
34
|
-
has: (name) => options.has(name),
|
|
35
|
-
one: (name) => options.get(name)?.at(-1),
|
|
36
|
-
many: (name) => options.get(name) || [],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const args = parseArgs(argv);
|
|
40
|
-
|
|
41
|
-
async function graphql(query, variables = {}) {
|
|
42
|
-
const key = process.env.LINEAR_API_KEY;
|
|
43
|
-
if (!key) die("LINEAR_API_KEY is not set", "Create a personal API key in Linear Settings → Security & access → API keys, export it, then run `oats linear auth`.");
|
|
44
|
-
let response;
|
|
45
|
-
try {
|
|
46
|
-
response = await fetch(API_URL, {
|
|
47
|
-
method: "POST",
|
|
48
|
-
headers: {
|
|
49
|
-
"Authorization": key,
|
|
50
|
-
"Content-Type": "application/json",
|
|
51
|
-
"User-Agent": "oats-linear/0.1",
|
|
52
|
-
},
|
|
53
|
-
body: JSON.stringify({ query, variables }),
|
|
54
|
-
signal: AbortSignal.timeout(30000),
|
|
55
|
-
});
|
|
56
|
-
} catch (error) {
|
|
57
|
-
die(`Linear API request failed: ${error.message || error}`);
|
|
58
|
-
}
|
|
59
|
-
const text = await response.text();
|
|
60
|
-
let payload;
|
|
61
|
-
try { payload = JSON.parse(text); }
|
|
62
|
-
catch { die(`Linear API returned HTTP ${response.status} with non-JSON content`, text.slice(0, 500)); }
|
|
63
|
-
if (!response.ok || payload.errors?.length) {
|
|
64
|
-
const errors = (payload.errors || []).map((error) => ({
|
|
65
|
-
message: error.extensions?.userPresentableMessage || error.message,
|
|
66
|
-
code: error.extensions?.code,
|
|
67
|
-
path: error.path,
|
|
68
|
-
}));
|
|
69
|
-
const hint = response.status === 401 ? "Check LINEAR_API_KEY and run `oats linear auth`." : undefined;
|
|
70
|
-
die(`Linear API request failed (HTTP ${response.status})`, { errors, ...(hint ? { hint } : {}) });
|
|
71
|
-
}
|
|
72
|
-
return payload.data;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const PAGE_INFO = "pageInfo { hasNextPage endCursor }";
|
|
76
|
-
const ISSUE_FIELDS = `
|
|
77
|
-
id identifier title description url priority priorityLabel
|
|
78
|
-
team { id key name }
|
|
79
|
-
state { id name type }
|
|
80
|
-
project { id name slugId }
|
|
81
|
-
parent { id identifier title }
|
|
82
|
-
assignee { id name email }
|
|
83
|
-
labels(first: 100) { nodes { id name } }
|
|
84
|
-
`;
|
|
85
|
-
|
|
86
|
-
async function teamByKey(key) {
|
|
87
|
-
if (!key || key === true) die("--team <KEY> is required");
|
|
88
|
-
const data = await graphql(`
|
|
89
|
-
query OatsLinearTeam($key: String!) {
|
|
90
|
-
teams(first: 2, filter: { key: { eqIgnoreCase: $key } }) { nodes { id key name } }
|
|
91
|
-
}
|
|
92
|
-
`, { key });
|
|
93
|
-
if (data.teams.nodes.length === 0) die(`Linear team "${key}" was not found`, "Run `oats linear teams` and use its key.");
|
|
94
|
-
if (data.teams.nodes.length > 1) die(`Linear team key "${key}" is ambiguous`);
|
|
95
|
-
return data.teams.nodes[0];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function statesForTeam(team) {
|
|
99
|
-
const data = await graphql(`
|
|
100
|
-
query OatsLinearStates($id: String!) {
|
|
101
|
-
team(id: $id) { states(first: 100) { nodes { id name type position } } }
|
|
102
|
-
}
|
|
103
|
-
`, { id: team.id });
|
|
104
|
-
return data.team.states.nodes.sort((a, b) => a.position - b.position);
|
|
105
|
-
}
|
|
106
|
-
async function stateByName(team, name) {
|
|
107
|
-
const states = await statesForTeam(team);
|
|
108
|
-
const matches = states.filter((state) => state.id === name || state.name.toLowerCase() === String(name).toLowerCase());
|
|
109
|
-
if (matches.length !== 1) die(`Workflow state "${name}" was not found for team ${team.key}`, { available: states.map((state) => `${state.name} (${state.type})`) });
|
|
110
|
-
return matches[0];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function labelsForTeam(team) {
|
|
114
|
-
const data = await graphql(`
|
|
115
|
-
query OatsLinearLabels($teamId: ID!) {
|
|
116
|
-
issueLabels(first: 250, filter: { or: [
|
|
117
|
-
{ team: { null: true } },
|
|
118
|
-
{ team: { id: { eq: $teamId } } }
|
|
119
|
-
] }) {
|
|
120
|
-
nodes { id name color isGroup team { id key } }
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
`, { teamId: team.id });
|
|
124
|
-
return data.issueLabels.nodes;
|
|
125
|
-
}
|
|
126
|
-
async function findLabel(team, name) {
|
|
127
|
-
const labels = await labelsForTeam(team);
|
|
128
|
-
const matches = labels.filter((label) => !label.isGroup && label.name.toLowerCase() === String(name).toLowerCase());
|
|
129
|
-
const scoped = matches.find((label) => label.team?.id === team.id);
|
|
130
|
-
return scoped || matches.find((label) => !label.team);
|
|
131
|
-
}
|
|
132
|
-
async function ensureAgentLabel(team, alias) {
|
|
133
|
-
const name = `agent-${alias}`;
|
|
134
|
-
const existing = await findLabel(team, name);
|
|
135
|
-
if (existing) return existing;
|
|
136
|
-
const data = await graphql(`
|
|
137
|
-
mutation OatsLinearCreateLabel($input: IssueLabelCreateInput!) {
|
|
138
|
-
issueLabelCreate(input: $input) { success issueLabel { id name color team { id key } } }
|
|
139
|
-
}
|
|
140
|
-
`, { input: { name, teamId: team.id, color: "#5E6AD2", description: "OATS agent instance identity" } });
|
|
141
|
-
if (!data.issueLabelCreate.success) die(`Linear did not create label "${name}"`);
|
|
142
|
-
return data.issueLabelCreate.issueLabel;
|
|
143
|
-
}
|
|
144
|
-
async function labelByName(team, name) {
|
|
145
|
-
const label = await findLabel(team, name);
|
|
146
|
-
if (!label) die(`Label "${name}" was not found for team ${team.key}`, "Create it in Linear first. Agent labels are created automatically by --agent.");
|
|
147
|
-
return label;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function projectsForTeam(team) {
|
|
151
|
-
const data = await graphql(`
|
|
152
|
-
query OatsLinearProjects($teamId: ID!) {
|
|
153
|
-
projects(first: 250, filter: { accessibleTeams: { some: { id: { eq: $teamId } } } }) {
|
|
154
|
-
nodes { id name slugId status { id name type } teams(first: 20) { nodes { id key name } } }
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
`, { teamId: team.id });
|
|
158
|
-
return data.projects.nodes;
|
|
159
|
-
}
|
|
160
|
-
async function projectByRef(team, ref) {
|
|
161
|
-
const projects = await projectsForTeam(team);
|
|
162
|
-
const needle = String(ref).toLowerCase();
|
|
163
|
-
const matches = projects.filter((project) =>
|
|
164
|
-
project.id === ref || project.slugId.toLowerCase() === needle || project.name.toLowerCase() === needle);
|
|
165
|
-
if (matches.length !== 1) die(`Project "${ref}" ${matches.length ? "is ambiguous" : "was not found"} for team ${team.key}`, { available: projects.map((project) => ({ name: project.name, slug: project.slugId })) });
|
|
166
|
-
return matches[0];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function issueById(id) {
|
|
170
|
-
if (!id || id === true) die("an issue identifier such as ENG-123 is required");
|
|
171
|
-
const data = await graphql(`
|
|
172
|
-
query OatsLinearIssue($id: String!) { issue(id: $id) { ${ISSUE_FIELDS} } }
|
|
173
|
-
`, { id });
|
|
174
|
-
return data.issue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function textOption(name) {
|
|
178
|
-
const inline = args.one(name);
|
|
179
|
-
const file = args.one(`${name}-file`);
|
|
180
|
-
if (inline !== undefined && file !== undefined) die(`use only one of --${name} or --${name}-file`);
|
|
181
|
-
if (file !== undefined) {
|
|
182
|
-
if (file === true) die(`--${name}-file needs a path`);
|
|
183
|
-
try { return readFileSync(file, "utf8").trim(); }
|
|
184
|
-
catch (error) { die(`cannot read --${name}-file ${file}: ${error.message}`); }
|
|
185
|
-
}
|
|
186
|
-
return inline;
|
|
187
|
-
}
|
|
188
|
-
function assertTerminalAllowed(state) {
|
|
189
|
-
if (["completed", "canceled", "duplicate"].includes(state.type) && !args.has("allow-terminal")) {
|
|
190
|
-
die(`refusing terminal state "${state.name}" without --allow-terminal`, "Agents should hand work to review, not close or cancel it. Use --allow-terminal only with explicit human authorization.");
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function auth() {
|
|
195
|
-
const data = await graphql(`
|
|
196
|
-
query OatsLinearAuth { viewer { id name email } organization { id name urlKey } }
|
|
197
|
-
`);
|
|
198
|
-
print({ authenticated: true, endpoint: API_URL, viewer: data.viewer, workspace: data.organization });
|
|
199
|
-
}
|
|
200
|
-
async function teams() {
|
|
201
|
-
const data = await graphql(`
|
|
202
|
-
query OatsLinearTeams { teams(first: 100) { nodes { id key name } } }
|
|
203
|
-
`);
|
|
204
|
-
print(data.teams.nodes);
|
|
205
|
-
}
|
|
206
|
-
async function states() {
|
|
207
|
-
const team = await teamByKey(args.one("team"));
|
|
208
|
-
print(await statesForTeam(team));
|
|
209
|
-
}
|
|
210
|
-
async function projects() {
|
|
211
|
-
const team = await teamByKey(args.one("team"));
|
|
212
|
-
print(await projectsForTeam(team));
|
|
213
|
-
}
|
|
214
|
-
async function labels() {
|
|
215
|
-
const team = await teamByKey(args.one("team"));
|
|
216
|
-
print(await labelsForTeam(team));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function listIssues() {
|
|
220
|
-
const team = await teamByKey(args.one("team"));
|
|
221
|
-
const requestedLimit = Number(args.one("limit") || 100);
|
|
222
|
-
if (!Number.isInteger(requestedLimit) || requestedLimit < 1 || requestedLimit > 250) die("--limit must be an integer from 1 to 250");
|
|
223
|
-
const filter = { team: { id: { eq: team.id } } };
|
|
224
|
-
if (!args.has("all")) filter.state = { type: { nin: ["completed", "canceled", "duplicate"] } };
|
|
225
|
-
if (args.one("agent")) filter.labels = { some: { name: { eqIgnoreCase: `agent-${args.one("agent")}` } } };
|
|
226
|
-
if (args.one("project")) {
|
|
227
|
-
const project = await projectByRef(team, args.one("project"));
|
|
228
|
-
filter.project = { id: { eq: project.id } };
|
|
229
|
-
}
|
|
230
|
-
const data = await graphql(`
|
|
231
|
-
query OatsLinearIssues($first: Int!, $filter: IssueFilter) {
|
|
232
|
-
issues(first: $first, filter: $filter) { nodes { ${ISSUE_FIELDS} } ${PAGE_INFO} }
|
|
233
|
-
}
|
|
234
|
-
`, { first: requestedLimit, filter });
|
|
235
|
-
print({ issues: data.issues.nodes, pageInfo: data.issues.pageInfo });
|
|
236
|
-
}
|
|
237
|
-
async function createIssue() {
|
|
238
|
-
const team = await teamByKey(args.one("team"));
|
|
239
|
-
const title = args.one("title");
|
|
240
|
-
if (!title || title === true) die("--title <text> is required");
|
|
241
|
-
let description = textOption("description");
|
|
242
|
-
const input = { teamId: team.id, title };
|
|
243
|
-
if (description !== undefined) input.description = description;
|
|
244
|
-
if (args.one("project")) input.projectId = (await projectByRef(team, args.one("project"))).id;
|
|
245
|
-
if (args.one("parent")) input.parentId = args.one("parent");
|
|
246
|
-
if (args.one("state")) {
|
|
247
|
-
const state = await stateByName(team, args.one("state"));
|
|
248
|
-
assertTerminalAllowed(state);
|
|
249
|
-
input.stateId = state.id;
|
|
250
|
-
}
|
|
251
|
-
const issueLabels = [];
|
|
252
|
-
if (args.one("agent")) {
|
|
253
|
-
const alias = args.one("agent");
|
|
254
|
-
issueLabels.push(await ensureAgentLabel(team, alias));
|
|
255
|
-
if (!/^Agent:/mi.test(description || "")) {
|
|
256
|
-
description = `${description ? `${description.trim()}\n\n` : ""}---\nAgent: ${alias}`;
|
|
257
|
-
input.description = description;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
for (const name of args.many("label")) issueLabels.push(await labelByName(team, name));
|
|
261
|
-
if (issueLabels.length) input.labelIds = [...new Set(issueLabels.map((label) => label.id))];
|
|
262
|
-
const data = await graphql(`
|
|
263
|
-
mutation OatsLinearIssueCreate($input: IssueCreateInput!) {
|
|
264
|
-
issueCreate(input: $input) { success issue { ${ISSUE_FIELDS} } }
|
|
265
|
-
}
|
|
266
|
-
`, { input });
|
|
267
|
-
if (!data.issueCreate.success || !data.issueCreate.issue) die("Linear did not create the issue");
|
|
268
|
-
print(data.issueCreate.issue);
|
|
269
|
-
}
|
|
270
|
-
async function updateIssue(id) {
|
|
271
|
-
const current = await issueById(id);
|
|
272
|
-
const team = current.team;
|
|
273
|
-
const input = {};
|
|
274
|
-
if (args.has("title")) input.title = args.one("title");
|
|
275
|
-
const description = textOption("description");
|
|
276
|
-
if (description !== undefined) input.description = description;
|
|
277
|
-
if (args.one("state")) {
|
|
278
|
-
const state = await stateByName(team, args.one("state"));
|
|
279
|
-
assertTerminalAllowed(state);
|
|
280
|
-
input.stateId = state.id;
|
|
281
|
-
}
|
|
282
|
-
const added = [];
|
|
283
|
-
if (args.one("agent")) added.push((await ensureAgentLabel(team, args.one("agent"))).id);
|
|
284
|
-
for (const name of args.many("add-label")) added.push((await labelByName(team, name)).id);
|
|
285
|
-
const removed = [];
|
|
286
|
-
for (const name of args.many("remove-label")) removed.push((await labelByName(team, name)).id);
|
|
287
|
-
if (added.length) input.addedLabelIds = [...new Set(added)];
|
|
288
|
-
if (removed.length) input.removedLabelIds = [...new Set(removed)];
|
|
289
|
-
if (Object.keys(input).length === 0) die("no update supplied", "Use --title, --description[-file], --state, --agent, --add-label, or --remove-label.");
|
|
290
|
-
const data = await graphql(`
|
|
291
|
-
mutation OatsLinearIssueUpdate($id: String!, $input: IssueUpdateInput!) {
|
|
292
|
-
issueUpdate(id: $id, input: $input) { success issue { ${ISSUE_FIELDS} } }
|
|
293
|
-
}
|
|
294
|
-
`, { id, input });
|
|
295
|
-
if (!data.issueUpdate.success || !data.issueUpdate.issue) die(`Linear did not update ${id}`);
|
|
296
|
-
print(data.issueUpdate.issue);
|
|
297
|
-
}
|
|
298
|
-
async function commentIssue(id) {
|
|
299
|
-
const body = textOption("body");
|
|
300
|
-
if (!body || body === true) die("--body <markdown> or --body-file <path> is required");
|
|
301
|
-
const data = await graphql(`
|
|
302
|
-
mutation OatsLinearComment($input: CommentCreateInput!) {
|
|
303
|
-
commentCreate(input: $input) { success comment { id body createdAt url user { id name } } }
|
|
304
|
-
}
|
|
305
|
-
`, { input: { issueId: id, body } });
|
|
306
|
-
if (!data.commentCreate.success) die(`Linear did not comment on ${id}`);
|
|
307
|
-
print(data.commentCreate.comment);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function usage() {
|
|
311
|
-
process.stderr.write(`oats linear commands (all output JSON):
|
|
312
|
-
auth
|
|
313
|
-
teams
|
|
314
|
-
states --team <KEY>
|
|
315
|
-
projects --team <KEY>
|
|
316
|
-
labels --team <KEY>
|
|
317
|
-
issue list --team <KEY> [--agent <alias>] [--project <name|slug>] [--all] [--limit 100]
|
|
318
|
-
issue get <KEY-123>
|
|
319
|
-
issue create --team <KEY> --title <text> [--description <md>|--description-file <path>]
|
|
320
|
-
[--project <name|slug>] [--parent <KEY-123>] [--state <name>] [--agent <alias>]
|
|
321
|
-
[--label <name> ...]
|
|
322
|
-
issue update <KEY-123> [--title <text>] [--description <md>|--description-file <path>]
|
|
323
|
-
[--state <name>] [--agent <alias>] [--add-label <name> ...] [--remove-label <name> ...]
|
|
324
|
-
[--allow-terminal]
|
|
325
|
-
issue comment <KEY-123> (--body <md>|--body-file <path>)
|
|
326
|
-
`);
|
|
327
|
-
process.exit(1);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (command === "auth") await auth();
|
|
331
|
-
else if (command === "teams") await teams();
|
|
332
|
-
else if (command === "states") await states();
|
|
333
|
-
else if (command === "projects") await projects();
|
|
334
|
-
else if (command === "labels") await labels();
|
|
335
|
-
else if (command === "issue") {
|
|
336
|
-
const subcommand = args.positional[0];
|
|
337
|
-
const id = args.positional[1];
|
|
338
|
-
if (subcommand === "list") await listIssues();
|
|
339
|
-
else if (subcommand === "get") print(await issueById(id));
|
|
340
|
-
else if (subcommand === "create") await createIssue();
|
|
341
|
-
else if (subcommand === "update") await updateIssue(id);
|
|
342
|
-
else if (subcommand === "comment") await commentIssue(id);
|
|
343
|
-
else usage();
|
|
344
|
-
} else usage();
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
## Tasks: Linear
|
|
2
|
-
|
|
3
|
-
Your tasks layer is **Linear**, operated through the JSON-first `oats linear`
|
|
4
|
-
commands. You are identified by the label `agent-<your-instance-name>`, not
|
|
5
|
-
by changing the human assignee. Load the **linear-tasks** skill before reading
|
|
6
|
-
your queue, creating or updating issues/sub-issues, changing status, or posting
|
|
7
|
-
handoffs. Tasks only: status and outcomes live in Linear; conversation lives
|
|
8
|
-
in your deployment's messaging layer.
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"capability": "oats.linear",
|
|
3
|
-
"command": "linear",
|
|
4
|
-
"version": "1.0.1",
|
|
5
|
-
"compatibility": { "oats": ">=0.26.0" },
|
|
6
|
-
"layer": "tasks",
|
|
7
|
-
"description": "Tasks layer via Linear: JSON-first GraphQL commands, project/issue/sub-issue workflow, label-based agent identity.",
|
|
8
|
-
"requires": [],
|
|
9
|
-
"skills": [
|
|
10
|
-
"skills"
|
|
11
|
-
],
|
|
12
|
-
"commands": {
|
|
13
|
-
"auth": "bin/oats-linear.mjs auth",
|
|
14
|
-
"teams": "bin/oats-linear.mjs teams",
|
|
15
|
-
"states": "bin/oats-linear.mjs states",
|
|
16
|
-
"projects": "bin/oats-linear.mjs projects",
|
|
17
|
-
"labels": "bin/oats-linear.mjs labels",
|
|
18
|
-
"issue": "bin/oats-linear.mjs issue"
|
|
19
|
-
},
|
|
20
|
-
"inject": "injects/linear.md",
|
|
21
|
-
"hooks": {
|
|
22
|
-
"spawn": "bin/oats-linear-hook.mjs spawn"
|
|
23
|
-
}
|
|
24
|
-
}
|