@openthink/team 0.0.11 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # open-team
2
2
 
3
- Source-agnostic vault-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.
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 ("vault" is Obsidian's word; oteam doesn't depend on Obsidian). Layout:
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 vault add <path>` or set `PRODUCT_VAULT_PATH`. For multiple workspaces (personal + work, etc.) see [Config & multiple vaults](#config--multiple-vaults).
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 vault add <path> # register a vault under a name
73
- oteam config vault list # show registered vaults + default
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 `--vault <name-or-path>` to operate on a specific vault.
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` | no-stamp | `git@github.com:<repo>.git` | Default. No stamp config files are read. `oteam` works against any git repo. |
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
- `oteam assign --no-stamp` is a per-run override: it forces the github clone path even when `stamp.enforce: true` is set. The persistent setting is `oteam config stamp set --enforce off`; `--no-stamp` is convenient when you want to spawn a one-off agent without touching config.
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 vaults
223
+ ## Config & multiple workspaces
229
224
 
230
- `open-team` supports any number of named vaults via `~/.open-team/config.json`. Register them with:
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 vault add ~/Documents/product-vault # auto-name "product-vault"; first add becomes default
234
- oteam config vault add ~/Documents/work-vault --name work
235
- oteam config vault list
236
- oteam config vault default --set work
237
- oteam config vault remove work # clears default if it pointed here
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
- Paths are resolved to absolute at `add` time, so the registration survives `cd`. Removing the default vault clears `default` and forces an explicit `--vault` on every subsequent command until you set a new one — there is no silent promotion.
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 | Notes |
245
- |---|---------------------------------------------------|------------------------------------------------------|
246
- | 1 | `--vault <name-or-path>` flag | Per-command override |
247
- | 2 | `PRODUCT_VAULT_PATH` env var | One-off shell override; also propagated to spawns |
248
- | 3 | `default` in `~/.open-team/config.json` | Set via `oteam config vault default --set <name>` |
249
- | 4 | `~/Documents/product-vault` | Implicit fallback if no config exists |
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 `<vault>/tickets/<state>/` for a file whose basename starts with `AGT-001-`.
254
- - **Vault auto-detection from path**: passing a full path that lives inside a registered vault root makes that vault the active one for the run, even if it's not the default. The spawned `_role-run` then inherits `PRODUCT_VAULT_PATH=<that-vault>` so any follow-up `oteam pull/list/...` from the agent lands in the same vault.
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 vault isn't at `~/Documents/product-vault`, or register it via `oteam config vault add <path>` (see [Config & multiple vaults](#config--multiple-vaults)).
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.
@@ -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 stamp server when `stamp.enforce: true` is set in `~/.open-team/config.json`, otherwise from GitHub directly; either way, 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.
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` (from the stamp server when `stamp.enforce: true` is set in `~/.open-team/config.json`; from GitHub otherwise, or when `--no-stamp` was passed) and set your cwd to it. Confirm with `pwd` and `git remote -v`; for stamp-governed repos you should see exactly one remote, `origin`, pointing at `ssh://git@<stamp-host>:<port>/srv/git/<basename>.git`.
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 stamp clone 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.
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
- SERVER_HOST=$(awk '/^host:/ { print $2 }' "$HOME/.stamp/server.yml" 2>/dev/null)
85
- SERVER_PORT=$(awk '/^port:/ { print $2 }' "$HOME/.stamp/server.yml" 2>/dev/null)
86
- if [ -n "$SERVER_HOST" ] && [ -n "$SERVER_PORT" ] && \
87
- git clone "ssh://git@${SERVER_HOST}:${SERVER_PORT}/srv/git/${REPO_BASE}.git" repo 2>/dev/null; then
88
- : # cloned from stamp; origin points at the stamp URL
89
- else
90
- # No stamp config or repo not on stamp server fall back to GitHub.
91
- git clone "git@github.com:${REPO_SLUG}.git" repo
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 (default).** Exactly one remote, `origin`, pointing at `ssh://git@<stamp-host>:<port>/srv/git/<basename>.git`. The runner clones with that shape on purpose; no rename / re-add is required, and adding a `github` remote here would defeat the AGT-050 invariant. `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 no stamp server is in use (typically a `--no-stamp` run against a stamp-aware repo). `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.
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-bound paths (plain, local-stamp): append a `Fixes <gh-issue-url>`
245
- # trailer so the eventual PR merge auto-closes the GH issue. The
246
- # stamp-governed path skips this trailer — those worktrees have no github
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.
@@ -0,0 +1,160 @@
1
+ ---
2
+ description: Drive every ticket in a vault project through the role-pipeline to done with minimal human intervention. Argument is a project id (e.g. `think-cli-v2`). Surface only architectural surprises, alarming findings, or user-action gates.
3
+ argument-hint: <project-id>
4
+ ---
5
+
6
+ You are running an **autonomous-orchestration loop** against a single project in the vault. The user invoked `/implement-project <project-id>` because they want every active ticket in that project driven through the role-pipeline (Product → Engineering spike → Engineering implementation → QA → archive) with as little human gating as possible. Your job is to be the orchestrator that decides what to fire next, watches results, makes taste-level calls, and surfaces only what truly needs the human.
7
+
8
+ **Argument**: `$ARGUMENTS` — a single project id matching a folder under `<vault>/projects/<id>/` (e.g. `think-cli-v2`). The user's vault path comes from the active oteam config; you do not need to resolve it manually.
9
+
10
+ ## Hard rules — read first
11
+
12
+ 1. **Stop conditions are tight. Everything else is yours.** Surface to the human only on:
13
+ - **Architectural decisions surfaced by unknown findings during build** — implementation reveals something that meaningfully changes the design (e.g. "the API doesn't actually support what we assumed").
14
+ - **Something so off or confusing it's alarming.** Trust the gut. Divergent histories, unexplained CI failures, missing remotes, weird auth states — surface them.
15
+ - **User-action gates** — tickets where the work is for the operator (e.g. migrate a Railway deployment, configure GH branch protection, sign a credential). You can't proxy these. Surface and wait.
16
+ - **Spike-time questions that require the user's roadmap or values** — not "default 100 vs 1000," but "single-tenant vs multi-tenant" if the design doc didn't already decide.
17
+
18
+ Do NOT stop for: file naming, test fixture shape, comment voice, choosing between equally-good library options, small refactors, ordering of independent sub-steps. Exercise judgment.
19
+
20
+ 2. **Push to main is self-authorized when stamp's three reviewers approve.** Do not ping the user before pushing. The stamp gate is the approval mechanism. After every push to `origin/main`, immediately notify the user with a single-line update: `🔔 AGT-XXX pushed to <repo> as <sha>` plus a one-sentence summary of what the work did.
21
+
22
+ 3. **Never push to GitHub directly from an agent worktree.** Work goes through stamp; stamp mirrors to GitHub. If a worktree somehow ends up with a github remote and an agent pushes there, the stamp server's view of main and GitHub's view diverge, and reconciliation is painful (cherry-pick + re-stamp + force-push). Insist on stamp-only push paths.
23
+
24
+ 4. **Background long runs, inline short ones.**
25
+ - **Inline:** Product passes, QA passes, anything < ~3 minutes.
26
+ - **Background** (`run_in_background: true` on the Bash tool): Engineering spikes, implementations, anything that involves real LLM thinking + code edits. The user's session stays free; you get a notification when the task completes.
27
+
28
+ 5. **Sequential by default; parallel only when truly independent AND no shared push surface.** Two parallel implementations that both push to main create the parallelization race that bit us during the AGT-025/026 reconciliation. Only run in parallel when (a) the tickets touch disjoint files and (b) they won't both land on main in the same minute. When in doubt, sequential.
29
+
30
+ 6. **3-attempt cap on any failing operation.** Three failures → STOP and surface the error.
31
+
32
+ ## Phase 0 — Pre-flight
33
+
34
+ Resolve the project. Run:
35
+
36
+ ```sh
37
+ oteam project show <project-id> --tickets
38
+ ```
39
+
40
+ If that errors, STOP — print `🛑 BLOCKED — project <id> not found in vault` and surface the candidates from `oteam project list`.
41
+
42
+ Read the project README and every sibling design doc:
43
+
44
+ ```sh
45
+ PROJECT_DIR="$(oteam project show <project-id> | grep '^ readme:' | awk '{print $2}' | xargs dirname)"
46
+ cat "$PROJECT_DIR/README.md"
47
+ ls "$PROJECT_DIR/"
48
+ # read each sibling .md as needed for context
49
+ ```
50
+
51
+ Build the dependency graph from ticket comments (each ticket usually names "Sequencing: blocked by AGT-XXX" or "depends on AGT-YYY"). Identify:
52
+
53
+ - Active tickets (state ≠ done): the work to do.
54
+ - Done tickets: assume their content is in main.
55
+ - User-action tickets (the kind where engineering can't proceed without the operator doing something offline): mark these as gates.
56
+ - Deferred / parking-lot tickets (their own comment usually says so): exclude from the chain unless the user says otherwise.
57
+
58
+ ## Phase 1 — Confirm the chain with the user
59
+
60
+ Print a short plan to the user (no walls of text):
61
+
62
+ - The active ticket list, in proposed execution order.
63
+ - Sequencing notes (which depend on which; which can run in parallel; which are user-gates).
64
+ - Anything explicitly excluded (deferred tickets) and why.
65
+
66
+ Ask one question: "Run this chain with the standard autonomous-orchestration rules? Or override anything?"
67
+
68
+ If the user says go (or equivalent), proceed. If they push back on the plan, adjust and re-confirm. Do not start firing without explicit go-ahead on the chain shape.
69
+
70
+ ## Phase 2 — Drive each ticket
71
+
72
+ For each non-user-gate ticket, in dependency order:
73
+
74
+ ### 2.0 — Brief on prior retros for this ticket's repo
75
+
76
+ **Gated on `repo:` being non-empty.** Read the ticket's `repo:` frontmatter field. If it is empty or absent, skip this step silently — vault-internal tickets have no cortex to brief from.
77
+
78
+ Derive the cortex name from the `repo:` value using the **same rule pinned in AGT-173/174**: take the path component after the slash, lowercased. Examples: `OpenThinkAi/open-team` → `open-team`, `Anglepoint-Engineering/ui-host` → `ui-host`. The cortex name is sourced from validated frontmatter, so it is safe as a literal in shell.
79
+
80
+ Run `think brief` and capture stdout. **Do not gate on exit code or empty output** — every failure mode (missing binary, cortex not found, non-zero exit, empty cortex) is non-fatal; the orchestrator proceeds without the brief:
81
+
82
+ ```sh
83
+ think brief --cortex <derived-cortex-name> 2>&1 || true
84
+ ```
85
+
86
+ Treat the captured output as a clearly-labelled background section in your working context: `## Prior context for ticket AGT-XXX (<repo>)`. It is background, not actionable directives — lessons to weigh when deciding how to drive this ticket, not a re-litigation of its spike. If `think` is missing, exits non-zero, or the cortex has no promoted retros yet, note `no prior retros yet for <repo>` and proceed normally.
87
+
88
+ **Fetch at most once per ticket per orchestrator run.** This step runs here at the 2.0 entry point; do not re-fetch in 2b, 2c, or 2d for the same ticket. The spawned `oteam assign` agent runs its own `think brief` via AGT-174 — do not forward this orchestrator's brief output into the spawn; that would double-fetch.
89
+
90
+ ### 2a. Product pass (inline, short)
91
+
92
+ ```sh
93
+ oteam assign --inline AGT-XXX
94
+ ```
95
+
96
+ If Product returns `✅ DONE — Refined; ready for Engineering spike`, proceed. If `⏸️ PAUSED — Ticket needs answers before Product can refine`, surface to the user — Product can't proceed without their input.
97
+
98
+ ### 2b. Engineering spike (background)
99
+
100
+ ```sh
101
+ oteam assign --inline AGT-XXX # with run_in_background: true on the Bash tool
102
+ ```
103
+
104
+ When the background task notification arrives, read the task's output file. Three possible outcomes:
105
+
106
+ - **Spike auto-proceeded to implementation (S/H rated, no plan review needed):** continue to 2d.
107
+ - **Spike paused for plan review (M+ scope or has gaps):** read the spike. Decide on each open question:
108
+ - If it's taste-level (file names, fixture shape, default values, sequencing of independent substeps): make the call yourself. Append a `### YYYY-MM-DD — Plan approved` comment to the ticket noting your decisions and the rationale. Advance the ticket frontmatter to `state: in-progress`, move the file to `tickets/in-progress/`, then re-fire `oteam assign --inline AGT-XXX` (background).
109
+ - If it's an architectural decision the design doc doesn't answer: surface to the user. Quote the spike's question. Wait.
110
+ - **Spike failed (`STOP:` error of some kind):** surface to the user with the error.
111
+
112
+ ### 2c. Implementation (background)
113
+
114
+ The agent does the work, runs stamp review, stamp merge, push. When the background task notification arrives:
115
+
116
+ - Read the task output.
117
+ - If it pushed to `origin/main`: notify the user immediately with a one-line `🔔 AGT-XXX pushed to <repo> as <sha>` plus a one-sentence summary.
118
+ - If it stamp-merged but the mirror push to GitHub was rejected (the AGT-026 case): SURFACE — divergence is alarming, never auto-reconcile.
119
+ - If stamp gate stayed closed (review never converged): surface the last review's `changes_requested` reasoning to the user.
120
+ - If implementation completed but didn't push (no main push needed, e.g. test-only changes that the agent decided to leave on a branch): note in the user-facing summary.
121
+
122
+ ### 2d. QA pass (inline, short)
123
+
124
+ ```sh
125
+ oteam assign --inline AGT-XXX
126
+ ```
127
+
128
+ QA verifies the AC against the merged code and archives the ticket. If QA returns `✅ DONE — QA approved; archived`, mark the ticket complete in your internal chain and move to the next ticket. If QA bounces back with `changes_requested`, fire the engineering implementation again (background) with the QA feedback noted in a comment.
129
+
130
+ ### 2e. User-action tickets
131
+
132
+ When the chain reaches a user-action ticket:
133
+
134
+ - Stop driving.
135
+ - Surface the ticket to the user with: title, what they need to do (concrete steps, ideally copy-pasteable commands), and why it's blocking.
136
+ - Wait. Do not proceed past it until the user signs off (either by archiving it or telling you "done, move on").
137
+
138
+ ## Phase 3 — Post-completion
139
+
140
+ When every active ticket in the chain is archived:
141
+
142
+ - Print a summary: tickets shipped (with SHAs), elapsed time, anything noteworthy that happened during the run.
143
+ - Offer to bump the project's `status:` frontmatter to `shipped` in `<project>/README.md`, if every active ticket is archived and the user hasn't otherwise indicated more work is incoming.
144
+ - Ask if there are any follow-up tickets to file (issues that surfaced during the run that didn't get filed yet).
145
+
146
+ Then stop. Do not chain into the next project automatically; the user picks what to do next.
147
+
148
+ ## Output discipline
149
+
150
+ - One status update per state transition. Don't narrate every Bash call.
151
+ - Always notify on push to `origin/main` (single line, immediate).
152
+ - When surfacing a stop condition: name the ticket, name the issue, propose the resolution paths, ask one question. No walls of text.
153
+ - Use the user's preferred voice from feedback memory: terse, technical, no marketing.
154
+
155
+ ## Things that are explicitly NOT your job
156
+
157
+ - Filing new tickets unsolicited (use `oteam ticket new` if a clear bug surfaces during the run, but only file when the issue is concrete and unrelated to the chain you're driving — don't bloat the chain).
158
+ - Reconciling stamp/GitHub divergence — surface immediately, do not auto-merge or auto-force-push.
159
+ - Rewriting design docs mid-run — if the design doc is wrong, surface that and wait.
160
+ - Pushing to feature branches that haven't been stamp-reviewed — stamp is the gate.