@openthink/team 0.0.12 → 0.0.13
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 +54 -31
- package/dist/assign-ticket.md +25 -21
- package/dist/index.js +883 -355
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# open-team
|
|
2
2
|
|
|
3
|
-
Source-agnostic
|
|
3
|
+
Source-agnostic workspace-driven role pipeline for spawning Claude agents against tickets. Lifts the "Assign to agent" + role-pipeline flow out of agentic-desktop's Swift code into a standalone npm CLI.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -42,7 +42,7 @@ Flags:
|
|
|
42
42
|
|
|
43
43
|
## Workspace setup
|
|
44
44
|
|
|
45
|
-
`open-team` reads from a workspace directory (
|
|
45
|
+
`open-team` reads from a workspace directory (no Obsidian required). Layout:
|
|
46
46
|
|
|
47
47
|
```
|
|
48
48
|
openteam/
|
|
@@ -56,7 +56,7 @@ openteam/
|
|
|
56
56
|
|
|
57
57
|
A ticket's `state:` frontmatter must always match its containing folder under `tickets/`.
|
|
58
58
|
|
|
59
|
-
For the simplest single-workspace setup, run `oteam init` (creates and registers `~/openteam/`). To use an existing tree, register it via `oteam config
|
|
59
|
+
For the simplest single-workspace setup, run `oteam init` (creates and registers `~/openteam/`). To use an existing tree, register it via `oteam config workspace add <path>` or set `PRODUCT_VAULT_PATH`. For multiple workspaces (personal + work, etc.) see [Config & multiple workspaces](#config--multiple-workspaces).
|
|
60
60
|
|
|
61
61
|
## Subcommands
|
|
62
62
|
|
|
@@ -65,12 +65,11 @@ oteam pull <source> <ref> # ingest external item → tickets/triage/
|
|
|
65
65
|
oteam pull --project <name> ... # tag the new ticket with a project
|
|
66
66
|
oteam assign <ticket-or-id> # drive role pipeline (full path or AGT-NNN)
|
|
67
67
|
oteam assign --inline <path> # … or run inline in current terminal
|
|
68
|
-
oteam assign --no-stamp <id> # one-shot override of stamp.enforce (clones from GitHub)
|
|
69
68
|
oteam list [--state <state>] # list active tickets
|
|
70
69
|
oteam list --project <name> # filter by project frontmatter
|
|
71
|
-
oteam archive <ticket-id> # move done ticket to archive/YYYY-MM/
|
|
72
|
-
oteam config
|
|
73
|
-
oteam config
|
|
70
|
+
oteam archive <ticket-id> # move done ticket to archive/YYYY-MM/ + reap workspace
|
|
71
|
+
oteam config workspace add <path> # register a workspace under a name
|
|
72
|
+
oteam config workspace list # show registered workspaces + default
|
|
74
73
|
oteam config stamp set --host <url> # configure stamp host post-init
|
|
75
74
|
oteam config stamp set --enforce on # require repos be stamp-registered
|
|
76
75
|
oteam config stamp clear # remove the stamp block
|
|
@@ -84,7 +83,7 @@ oteam telemetry summary [--days N] [--phase X] [--model Y]
|
|
|
84
83
|
oteam telemetry tail [-n 20] # last N telemetry lines (raw JSONL)
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
Most commands accept `--
|
|
86
|
+
Most commands accept `--workspace <name-or-path>` (or the back-compat alias `--vault`) to operate on a specific workspace.
|
|
88
87
|
|
|
89
88
|
### Tagging tickets by project
|
|
90
89
|
|
|
@@ -141,7 +140,7 @@ Where the clone comes from is governed by oteam config (`~/.open-team/config.jso
|
|
|
141
140
|
|
|
142
141
|
| `stamp` config | Mode | Clone source | Behaviour |
|
|
143
142
|
|------------------------------------------------|-----------|--------------------------------------------------------|-------------------------------------------------------------------------------------------------|
|
|
144
|
-
| absent / `null` |
|
|
143
|
+
| absent / `null` | plain | `git@github.com:<repo>.git` | Default. No stamp config files are read. `oteam` works against any git repo. |
|
|
145
144
|
| `{ host, enforce: false }` | soft | `git@github.com:<repo>.git` | Stamp host is recorded for tooling that asks for it; `oteam assign` does not gate. |
|
|
146
145
|
| `{ host, enforce: true }` | enforce | `<host>/srv/git/<basename>.git` (the stamp server) | The clone IS the gate: clone failure exits non-zero before any spawn. AGT-050 behaviour. |
|
|
147
146
|
|
|
@@ -157,11 +156,7 @@ oteam config stamp set --enforce off # … or back off
|
|
|
157
156
|
oteam config stamp clear # remove the stamp block entirely
|
|
158
157
|
```
|
|
159
158
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
> **Migration note.** Earlier `oteam` builds read `~/.stamp/server.yml` directly. This version does not — to keep the AGT-050 stamp gate in place after upgrade, run `oteam init` and paste the host (or `oteam config stamp set --host <url> --enforce on`).
|
|
163
|
-
|
|
164
|
-
Stale workspaces from prior assigns are GC'd at spawn time: any `/tmp/open-team-issues/agt-N/` directory whose ticket id has no matching ticket in the active vault is `rm -rf`'d before the new clone. The current run's workspace is also `rm -rf`'d before its clone, so re-assigns are hermetic.
|
|
159
|
+
Stale workspaces from prior assigns are GC'd at spawn time: any `/tmp/open-team-issues/agt-N/` directory whose ticket id has no matching ticket in the active workspace is `rm -rf`'d before the new clone. The current run's workspace is also `rm -rf`'d before its clone, so re-assigns are hermetic.
|
|
165
160
|
|
|
166
161
|
## Per-phase model selection
|
|
167
162
|
|
|
@@ -225,33 +220,61 @@ Knobs:
|
|
|
225
220
|
- `oteam config telemetry set off` opts out — no JSONL writes occur. Re-enable with `oteam config telemetry set on`. Default is on.
|
|
226
221
|
- Telemetry is best-effort: a write failure (read-only dir, missing session file, malformed log) writes one line to stderr and does not fail the role-pipeline phase. Token fields the agent SDK doesn't expose are omitted from the line rather than recorded as zero.
|
|
227
222
|
|
|
228
|
-
## Config & multiple
|
|
223
|
+
## Config & multiple workspaces
|
|
229
224
|
|
|
230
|
-
`open-team` supports any number of named
|
|
225
|
+
`open-team` supports any number of named workspaces via `~/.open-team/config.json`. The on-disk key is `vaults` for back-compat with earlier builds; conceptually these are workspaces. Register them with:
|
|
231
226
|
|
|
232
227
|
```sh
|
|
233
|
-
oteam config
|
|
234
|
-
oteam config
|
|
235
|
-
oteam config
|
|
236
|
-
oteam config
|
|
237
|
-
oteam config
|
|
228
|
+
oteam config workspace add ~/Documents/my-workspace # auto-name "my-workspace"; first add becomes default
|
|
229
|
+
oteam config workspace add ~/Documents/work-workspace --name work
|
|
230
|
+
oteam config workspace list
|
|
231
|
+
oteam config workspace default --set work
|
|
232
|
+
oteam config workspace remove work # clears default if it pointed here
|
|
238
233
|
```
|
|
239
234
|
|
|
240
|
-
|
|
235
|
+
The `oteam config vault ...` form also works as a silent back-compat alias (`oteam config vault add` and `oteam config workspace add` register the same thing).
|
|
236
|
+
|
|
237
|
+
Paths are resolved to absolute at `add` time, so the registration survives `cd`. Removing the default workspace clears `default` and forces an explicit `--workspace` on every subsequent command until you set a new one — there is no silent promotion.
|
|
241
238
|
|
|
242
239
|
### Resolution precedence (most-specific wins)
|
|
243
240
|
|
|
244
|
-
| # | Source
|
|
245
|
-
|
|
246
|
-
| 1 | `--
|
|
247
|
-
| 2 | `PRODUCT_VAULT_PATH` env var
|
|
248
|
-
| 3 | `default` in `~/.open-team/config.json`
|
|
249
|
-
| 4 | `~/Documents/product-vault`
|
|
241
|
+
| # | Source | Notes |
|
|
242
|
+
|---|-----------------------------------------------------------|---------------------------------------------------------------|
|
|
243
|
+
| 1 | `--workspace <name-or-path>` flag (or `--vault` alias) | Per-command override |
|
|
244
|
+
| 2 | `PRODUCT_VAULT_PATH` env var | One-off shell override; also propagated to spawns |
|
|
245
|
+
| 3 | `default` in `~/.open-team/config.json` | Set via `oteam config workspace default --set <name>` |
|
|
246
|
+
| 4 | `~/Documents/product-vault` | Implicit fallback if no config exists |
|
|
250
247
|
|
|
251
248
|
`oteam assign` adds two niceties on top:
|
|
252
249
|
|
|
253
|
-
- **AGT-NNN shorthand**: `oteam assign AGT-001` walks `<
|
|
254
|
-
- **
|
|
250
|
+
- **AGT-NNN shorthand**: `oteam assign AGT-001` walks `<workspace>/tickets/<state>/` for a file whose basename starts with `AGT-001-`.
|
|
251
|
+
- **Workspace auto-detection from path**: passing a full path that lives inside a registered workspace root makes that workspace the active one for the run, even if it's not the default. The spawned `_role-run` then inherits `PRODUCT_VAULT_PATH=<that-workspace>` so any follow-up `oteam pull/list/...` from the agent lands in the same workspace.
|
|
252
|
+
|
|
253
|
+
## Claim-on-assign (preventing double-pickup)
|
|
254
|
+
|
|
255
|
+
When multiple operators or agents work the same workspace, two of them can race on the same ticket — both run `oteam assign` and both spin up role pipelines against the same GitHub issue. To prevent that, `oteam assign` can claim the underlying GH issue (sets `assignees`) before driving the pipeline:
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
oteam config bot-identity set <github-login> # e.g. your own login, or a dedicated bot account
|
|
259
|
+
oteam config bot-identity show
|
|
260
|
+
oteam config bot-identity clear # disable claim-on-assign
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
When `botIdentity` is set, every `oteam assign` run on a github-sourced ticket does the following pre-flight before any expensive setup:
|
|
264
|
+
|
|
265
|
+
1. GET the issue from `source.url`.
|
|
266
|
+
2. If state is `closed` → exit 1 (refuses to drive the pipeline on resolved work).
|
|
267
|
+
3. If already assigned to someone other than `botIdentity` → exit 1 (someone else has it).
|
|
268
|
+
4. PATCH `assignees: [botIdentity]`. Re-read the response.
|
|
269
|
+
5. If assignees came back empty, the operator's `gh` token has no push access on the repo (GitHub silently drops assignee changes without it) → exit 1 with a hint.
|
|
270
|
+
6. If assignees != `[botIdentity]` (race with another writer) → exit 1.
|
|
271
|
+
7. Otherwise the claim is held; proceed.
|
|
272
|
+
|
|
273
|
+
When `botIdentity` is empty (the default), the pre-flight is skipped — backwards-compatible with configs that predate the field.
|
|
274
|
+
|
|
275
|
+
Per-invocation override: `OTEAM_BOT_IDENTITY=<login> oteam assign …`. Useful when one operator runs occasionally under a different identity (e.g. testing with a personal account) without rewriting the persisted config.
|
|
276
|
+
|
|
277
|
+
Manual tickets (`source.type: manual`) and tickets with no parseable GH URL skip the claim entirely — there's nothing to claim.
|
|
255
278
|
|
|
256
279
|
## Migration from agentic-desktop
|
|
257
280
|
|
|
@@ -260,7 +283,7 @@ agentic-desktop now keeps only the PR-side modules (`GitHubPRs`, `AIReview*`, `C
|
|
|
260
283
|
Migration steps:
|
|
261
284
|
|
|
262
285
|
1. `npm install -g @openthink/team` (or `npm link` from a local clone).
|
|
263
|
-
2. Either set `PRODUCT_VAULT_PATH` if your
|
|
286
|
+
2. Either set `PRODUCT_VAULT_PATH` if your workspace isn't at `~/Documents/product-vault`, or register it via `oteam config workspace add <path>` (see [Config & multiple workspaces](#config--multiple-workspaces)).
|
|
264
287
|
3. Optionally set `OTEAM_MONITORED_ORGS=Org1,Org2` to route those repos' tickets to the "work" kitty socket (preserves the personal/work split agentic-desktop had).
|
|
265
288
|
4. Delete `~/Library/Application Support/AgenticDesktop/vault-assignments.json` (panel-indicator state, no longer used).
|
|
266
289
|
5. Use `oteam pull github <ref>` instead of clicking "Assign to agent" on the Issues panel.
|
package/dist/assign-ticket.md
CHANGED
|
@@ -13,7 +13,7 @@ You are working a `product-vault` ticket. The user invoked `oteam assign <path>`
|
|
|
13
13
|
3. **No commits, no PRs, no Linear.** Vault tickets do not necessarily map to a code repo. Only act on code if the ticket's `repo:` field is set AND the work demands it.
|
|
14
14
|
4. **STOP at every role-handoff boundary.** When your role is done, write a STOP marker (visual banner per Output discipline below) and let the human decide whether to continue.
|
|
15
15
|
5. **3-attempt cap on any failing operation.** If a step fails (e.g., file mv fails, frontmatter parse fails, build/test fails), you get 3 tries before STOPPing.
|
|
16
|
-
6. **Never read or write inside `$HOME/Development/<repo>`.** That tree may have uncommitted in-flight work; entangling with it is a sterile-field violation. For repo-bound tickets, the `oteam` runner has already prepared an isolated agent worktree at `/tmp/open-team-issues/<ticket-id-lowercased>/repo` and spawned you cd'd into it — that's your only valid working directory. The runner clones from the
|
|
16
|
+
6. **Never read or write inside `$HOME/Development/<repo>`.** That tree may have uncommitted in-flight work; entangling with it is a sterile-field violation. For repo-bound tickets, the `oteam` runner has already prepared an isolated agent worktree at `/tmp/open-team-issues/<ticket-id-lowercased>/repo` and spawned you cd'd into it — that's your only valid working directory. The runner clones from the URI recorded for the repo in `~/.open-team/config.json` and sets your cwd to it; the worktree is isolated from your primary, so AC-shaped requirements like "primary's `git remote -v` is byte-equal before/after a spawn" are satisfied by construction. If your `$PWD` is not the prepared workspace (e.g. you invoked the slash command by hand outside of `oteam assign`), set up the workspace yourself before reading any repo file — see Phase 3 Step 0.
|
|
17
17
|
|
|
18
18
|
## Phase 0 — Read the ticket
|
|
19
19
|
|
|
@@ -65,9 +65,9 @@ If your appended system context flags the **AGT-107 haiku-downshift heuristic**
|
|
|
65
65
|
|
|
66
66
|
## Phase 3 — Engineering agent (state: refined → spike phase)
|
|
67
67
|
|
|
68
|
-
**Step 0 — Workspace is already prepared.** When `oteam assign` spawned you against a repo-bound ticket, it already cloned `/tmp/open-team-issues/<ticket-id-lowercased>/repo`
|
|
68
|
+
**Step 0 — Workspace is already prepared.** When `oteam assign` spawned you against a repo-bound ticket, it already cloned `/tmp/open-team-issues/<ticket-id-lowercased>/repo` from the URI recorded for the repo in `~/.open-team/config.json` and set your cwd to it. Confirm with `pwd` and `git remote -v`; you should see exactly one remote, `origin`, pointing at the URI recorded for this repo.
|
|
69
69
|
|
|
70
|
-
Cost trade: a fresh
|
|
70
|
+
Cost trade: a fresh clone per assign adds a few seconds vs. the older `git worktree add` fast path. That's an intentional trade for AC-grade isolation — the agent worktree shares no `.git/objects` and no remotes with your primary, and removing or renaming any remote inside the worktree cannot leak back to your daily flow.
|
|
71
71
|
|
|
72
72
|
If you invoked `/assign-ticket` by hand (no `oteam assign` wrapper) and the workspace doesn't exist yet, set it up the same way the runner would:
|
|
73
73
|
|
|
@@ -77,19 +77,19 @@ WORKSPACE="/tmp/open-team-issues/$TICKET_ID_LC"
|
|
|
77
77
|
# REPO_SLUG is "<owner>/<name>" from `repo:` if set, else inferred from the
|
|
78
78
|
# ticket. When inferring, name it explicitly in your spike notes so the human
|
|
79
79
|
# can correct you.
|
|
80
|
-
REPO_BASE=$(basename "$REPO_SLUG")
|
|
81
80
|
mkdir -p "$WORKSPACE"
|
|
82
81
|
cd "$WORKSPACE"
|
|
83
82
|
rm -rf repo
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
#
|
|
91
|
-
|
|
83
|
+
# Look up the recorded clone URI. `oteam config repo show` prints two lines
|
|
84
|
+
# (clone-uri: ..., added: ...) on success, or "(no entry for ...)" on miss.
|
|
85
|
+
CLONE_URI=$(oteam config repo show "$REPO_SLUG" 2>/dev/null | awk '/^clone-uri:/ { print $2 }')
|
|
86
|
+
if [ -z "$CLONE_URI" ]; then
|
|
87
|
+
# No recorded URI yet — fall back to the GitHub HTTPS default. Running
|
|
88
|
+
# `oteam assign` interactively will prompt once and record it; hand-running
|
|
89
|
+
# this slash command skips the prompt and uses the public default.
|
|
90
|
+
CLONE_URI="git@github.com:${REPO_SLUG}.git"
|
|
92
91
|
fi
|
|
92
|
+
git clone -- "$CLONE_URI" repo
|
|
93
93
|
cd repo
|
|
94
94
|
```
|
|
95
95
|
|
|
@@ -175,8 +175,8 @@ Read `CLAUDE.md`, `AGENTS.md`, `README.md` at the repo root if present.
|
|
|
175
175
|
|
|
176
176
|
**2. Verify the worktree shape and compute the merge mode.** Run `git remote -v` and check whether `.stamp/` exists in the worktree. Three shapes are valid:
|
|
177
177
|
|
|
178
|
-
- **Stamp-governed
|
|
179
|
-
- **Local-stamp.** `origin` points at `git@github.com:<owner>/<repo>.git` AND the worktree contains a `.stamp/` directory. The repo carries stamp config but
|
|
178
|
+
- **Stamp-governed.** Exactly one remote, `origin`, pointing at a non-GitHub URI (the stamp server). `MODE` will resolve to `stamp` and routing goes through the stamp-protected branch (5a) at the end of Step 5.
|
|
179
|
+
- **Local-stamp.** `origin` points at `git@github.com:<owner>/<repo>.git` AND the worktree contains a `.stamp/` directory. The repo carries stamp config but `origin` is GitHub, not a stamp server. `MODE` will resolve to `local-stamp` and routing goes through 5c — `stamp review` + `stamp merge` produce a signed merge commit locally, which is then pushed to GitHub as the PR head for human review. `stamp push` is intentionally not invoked (no stamp server); `stamp verify <merge-sha>` still works against the PR head from any clone with the trusted public keys.
|
|
180
180
|
- **Plain GitHub.** `origin` points at `git@github.com:<owner>/<repo>.git` and there is no `.stamp/` directory. `MODE` will resolve to `plain` and routing goes through 5b — `git push origin <feature>` + `gh pr create`.
|
|
181
181
|
|
|
182
182
|
Compute `MODE` once, here, from the worktree's actual state — every subsequent step branches on this single variable:
|
|
@@ -241,13 +241,9 @@ When tests pass:
|
|
|
241
241
|
```sh
|
|
242
242
|
git add -A
|
|
243
243
|
COMMIT_BODY="Refs <ticket-id>"
|
|
244
|
-
# GitHub
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
# remote, the merge gets pushed to the stamp server, and GH never sees a
|
|
248
|
-
# commit that would trigger auto-close. QA Phase 5 closes the GH issue
|
|
249
|
-
# explicitly via `gh issue close`, so behaviour is preserved either way.
|
|
250
|
-
if [ "<source.type>" = "github" ] && [ "$MODE" != "stamp" ]; then
|
|
244
|
+
# When the source is a GitHub issue, append a `Fixes <gh-issue-url>` trailer
|
|
245
|
+
# so the PR merge (or stamp push that mirrors to GitHub) auto-closes the issue.
|
|
246
|
+
if [ "<source.type>" = "github" ]; then
|
|
251
247
|
COMMIT_BODY="$COMMIT_BODY"$'\n'"Fixes <linked-github URL>"
|
|
252
248
|
fi
|
|
253
249
|
git commit -m "<one-line summary>
|
|
@@ -260,6 +256,14 @@ Never use `--no-verify`. Fix hook failures at the root cause.
|
|
|
260
256
|
|
|
261
257
|
**5. Route by `$MODE`** (set in Step 2): `stamp` → 5a, `plain` → 5b, `local-stamp` → 5c.
|
|
262
258
|
|
|
259
|
+
**Push gate (AGT-099).** If your appended system context includes a `# Push step: disabled by oteam config` block, the operator has set `push: off` in `~/.open-team/config.json`. Run every step in 5a/5b/5c up to (but not including) the outbound push command, then print:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
push disabled by oteam config; merge commit is local at <sha>; run 'git push origin' manually when ready
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
substituting `<sha>` with `git rev-parse HEAD` after the merge (5a/5c) or after the last feature commit (5b). In 5b/5c, also skip `gh pr create` and leave `linked-pr:` empty — there is no pushed branch for the PR to reference. Note the held push in the wrap-up comment. Step 6 (stamp retro routing) still runs because it does not depend on the push.
|
|
266
|
+
|
|
263
267
|
#### 5a — Stamp-protected repo
|
|
264
268
|
|
|
265
269
|
Run review and merge. Capture the review's combined output (stdout + stderr) to a known tempfile so Step 6 can route any `STAMP-RETRO` candidates the reviewers emit. Re-run the entire `tee` block on every round of the 5-round iteration — `$STAMP_REVIEW_OUT` is reassigned to a fresh `mktemp` each round, so Step 6 reads only the last (gate-opening) run; prior tempfiles are left behind for the OS to reap.
|