solo-cto-agent 1.3.2 → 1.4.0

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +96 -31
  3. package/bin/cli.js +118 -3
  4. package/bin/do.js +210 -0
  5. package/bin/lib/nl-orchestrator.js +237 -0
  6. package/bin/lib/telegram-commands.js +759 -0
  7. package/bin/repo-discovery.js +234 -0
  8. package/bin/telegram-bot.js +154 -5
  9. package/bin/wizard.js +39 -1
  10. package/docs/codex-main-install.md +36 -12
  11. package/docs/hero-banner-prompt.md +85 -0
  12. package/docs/user-journey.md +369 -0
  13. package/package.json +2 -2
  14. package/skills/orchestrate/SKILL.md +30 -16
  15. package/templates/orchestrator/.github/workflows/auto-diagnose.yml +15 -1
  16. package/templates/orchestrator/.github/workflows/combined-pr-with-uiux.yml +102 -69
  17. package/templates/orchestrator/.github/workflows/cross-review-dispatch.yml +11 -0
  18. package/templates/orchestrator/.github/workflows/cross-review.yml +4 -0
  19. package/templates/orchestrator/.github/workflows/nl-processor.yml +59 -0
  20. package/templates/orchestrator/.github/workflows/pr-merge-notify.yml +86 -0
  21. package/templates/orchestrator/.github/workflows/rework-auto.yml +24 -0
  22. package/templates/orchestrator/.github/workflows/visual-check.yml +13 -3
  23. package/templates/orchestrator/.github/workflows/visual-report.yml +179 -0
  24. package/templates/orchestrator/api/telegram-commands.js +759 -0
  25. package/templates/orchestrator/api/telegram-webhook.js +84 -0
  26. package/templates/orchestrator/ops/agents/claude-reviewer.js +1 -1
  27. package/templates/orchestrator/ops/agents/claude-worker.js +1 -1
  28. package/templates/orchestrator/ops/agents/cross-reviewer.js +693 -68
  29. package/templates/orchestrator/ops/agents/nl-processor.js +165 -0
  30. package/templates/orchestrator/ops/agents/rework-agent.js +178 -8
  31. package/templates/orchestrator/ops/agents/visual-reporter.js +445 -0
  32. package/templates/orchestrator/ops/lib/nl-orchestrator.js +237 -0
  33. package/templates/orchestrator/ops/lib/route-detection.js +135 -0
  34. package/templates/orchestrator/ops/lib/screenshot-providers/browserless-provider.js +72 -0
  35. package/templates/orchestrator/ops/lib/screenshot-providers/playwright-provider.js +67 -0
  36. package/templates/orchestrator/ops/scripts/auto-diagnose.js +80 -13
  37. package/templates/orchestrator/ops/scripts/visual-check.js +66 -13
  38. package/templates/product-repo/.github/workflows/solo-cto-pipeline.yml +50 -22
  39. package/tiers.json +7 -5
  40. package/templates/product-repo/.github/workflows/cross-review-dispatch.yml +0 -27
  41. package/templates/product-repo/.github/workflows/rework-dispatch.yml +0 -51
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.4.0 (2026-04-19)
4
+
5
+ **Theme**: End-to-end automation complete. Install is fully automatic; review → rework → visual → merge runs without human copy-paste; Telegram / Discord carry the full operational loop.
6
+
7
+ ### Highlights
8
+ * **Natural-language work orders** — `solo-cto-agent do "..."` CLI + Telegram `/do` route a plain-English instruction to the right product repo as a labeled, spec-rich issue the existing worker pipeline picks up.
9
+ * **3-round agent consensus** — `cross-reviewer.js` runs an A/B debate (R1 propose → R2 agree/disagree/add → R3 verdict) with early-exit on agreement; non-consensus after R3 still dispatches rework with a distinguishable reason.
10
+ * **Before/After visual report** — new `visual-report.yml` + `visual-reporter.js` capture screenshots of the Vercel preview at the pre- and post-rework SHA, compose side-by-side PNGs, commit them to the orchestrator, post to PR + Telegram `sendMediaGroup`.
11
+ * **Opt-in GitHub auto-merge** — PR with `auto-merge-when-ready` label is merged by GitHub the moment all required checks pass (native `enablePullRequestAutoMerge` mutation; branch protection respected).
12
+ * **Unified dispatcher** — `solo-cto-pipeline.yml` is now the single product-repo dispatcher with 7-layer anti-loop guards. Legacy `cross-review-dispatch.yml` + `rework-dispatch.yml` deleted; concurrency guards added on orchestrator receivers.
13
+ * **Full install automation** — `setup.sh` now creates the orchestrator repo on GitHub, pushes it, and sets the `TRACKED_REPOS` variable itself instead of printing copy-paste commands.
14
+ * **Telegram CTO command surface** — `/status`, `/list`, `/rework`, `/approve`, `/do`, `/digest`, `/merge` (admin-gated). Every review / rework / report message includes inline buttons for ✅ Approve · ❌ Reject · 🔧 Rework · 🔀 Merge.
15
+ * **Discord mirror** — set `DISCORD_WEBHOOK_URL` and visual-change screenshots / auto-diagnose reports mirror to Discord as file attachments.
16
+ * **Repo auto-discovery** — `init --wizard` shells out to `gh api` and offers a multi-select of the user's repos; saved selection auto-fills `--repos` on every subsequent command.
17
+
18
+ ### Pipeline fixes
19
+ * `review-request` dispatch no longer orphaned — solo-cto-pipeline now emits `cross-review` to match the existing orchestrator listener.
20
+ * Anti-loop guards in solo-cto-pipeline extended to recognise new comment formats: `## 🔍 Consensus Review`, `## Visual Report — Before / After`, `[visual-report-skipped:…]`, circuit-breaker comments, auto-merge-enabled comments, `<!-- cross-reviewer:consensus -->` machine tag.
21
+ * Claude model IDs unified to `claude-sonnet-4-6` across rework-agent, claude-reviewer, claude-worker (was mixed 4.0 / 4.6).
22
+ * Hardcoded `seunghunbae-3svs` owner in `solo-cto-pipeline.yml` replaced with `{{GITHUB_OWNER}}` placeholder — every non-maintainer user was hitting silent dispatch failures.
23
+ * OpenAI call in rework-agent was passing `system` as a top-level parameter (wrong shape); moved into `messages[]` as a system-role message.
24
+
25
+ ### New notification paths
26
+ * **`pr-merge-notify.yml`** — fires on PR closed (merged or not); posts consolidated Telegram + Discord summary with rework round count.
27
+ * **`combined-pr-with-uiux.yml` rewired** — now triggers on the real workflow name (`Visual Report (Before/After)`), posts the single "all agent checks passed" message exactly once per PR.
28
+ * `visual-check.yml` fires on `workflow_run: Auto Rework on Review completed` → fresh preview screenshots after every rework.
29
+ * `auto-diagnose.yml` fires on rework-auto **failure** → Telegram-attached JSON diagnostic.
30
+ * Skip paths (`[visual-report-skipped:…]`) now notify Telegram/Discord too so silent failures aren't silent.
31
+
32
+ ### Docs + hygiene
33
+ * README front section rewritten to describe the full pipeline (consensus, rework, visual, Telegram) instead of just dual-agent review.
34
+ * New `docs/user-journey.md` — install → trigger → review → rework → visual → merge, with ASCII flow diagram, common scenarios, troubleshooting table.
35
+ * `docs/hero-banner-prompt.md` (new) — regeneration prompt for the README hero image aligned with the expanded surface.
36
+ * `.env.example` covers Discord / Vercel / Browserless / admin Telegram chat IDs.
37
+ * PAT scope guidance expanded for classic vs fine-grained tokens.
38
+ * `require.main === module` guards on `visual-check.js` + `auto-diagnose.js` so tests can import them without firing `main()`.
39
+ * Audit report from 2026-04-19 published as a gist: https://gist.github.com/seunghunbae-3svs/4f3da08f149fdb2b2451b43751f6f35c
40
+
41
+ ### Merged PRs
42
+ * #106 — vision batch (repo-discovery / consensus / visual-report / NL orders / Telegram CTO)
43
+ * #107 — pipeline consolidation
44
+ * #108 — docs sync with #106/#107
45
+ * #109 — end-to-end loop closure (install automation, merge notifications, D1/D2 wiring, README)
46
+ * (this release) — `require.main` guards + hero banner prompt + de-dupe combined-pr-with-uiux trigger
47
+
48
+ ---
49
+
3
50
  ## v1.3.0 (2026-04-17)
4
51
 
5
52
  **Theme**: Tier 3 deep integration features — plugin registry search, setup automation, type system enhancements.
@@ -121,6 +168,20 @@ non-interactive verify in CI, and tear it all down with one command.
121
168
 
122
169
  ## Unreleased
123
170
 
171
+ * Merge pull request #109 from seunghunbae-3svs/claude/e2e-audit
172
+
173
+ * Merge pull request #108 from seunghunbae-3svs/claude/docs-sync
174
+
175
+ * Merge pull request #107 from seunghunbae-3svs/claude/pipeline-consolidation
176
+
177
+ * Merge pull request #106 from seunghunbae-3svs/claude/vision-batch
178
+
179
+ * Merge pull request #105 from seunghunbae-3svs/claude/friendly-black-efec58
180
+
181
+ * docs: sharpen README intro — feature-first, install in 2 lines
182
+
183
+ * chore: v1.3.2 — clean up README slop, sync versions, update metrics
184
+
124
185
  * ci: add VS Code extension auto-publish to release workflow
125
186
 
126
187
  * docs: add hero banner to README, update test badge to 996
package/README.md CHANGED
@@ -4,46 +4,103 @@
4
4
 
5
5
  # solo-cto-agent
6
6
 
7
- **Dual-agent code review, secret detection, and circuit breakers for solo founders.**
7
+ **The full CTO loop for solo founders — dual-agent review, multi-turn consensus, auto-rework, before/after visual reports, and a Telegram/Discord control surface.**
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/solo-cto-agent)](https://www.npmjs.com/package/solo-cto-agent)
10
10
  [![Test](https://img.shields.io/badge/tests-996%20passing-brightgreen)](https://github.com/seunghunbae-3svs/solo-cto-agent/actions/workflows/test.yml)
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
12
12
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
13
13
 
14
- > Stop babysitting your AI agent. `solo-cto-agent` adds circuit breakers for error loops, dual-agent code review, design quality gates, session memory, and deployment checklists so you can focus on building instead of supervising.
14
+ You push code. Two AI agents review it, debate for up to three rounds until they reach consensus, auto-push fixes for any blockers, shoot before/after screenshots of your Vercel preview, and ping your phone on Telegram with action buttons. When you tap Merge (or set the `auto-merge-when-ready` label), GitHub merges it once CI is green. You stay in the loop from your phone; you never touch YAML.
15
15
 
16
- **For solo founders, indie hackers, and small teams using Claude Cowork + OpenAI Codex.**
16
+ ```bash
17
+ npm i -g solo-cto-agent
18
+ solo-cto-agent init --wizard # pick your repos, pick your tier
19
+ solo-cto-agent do "fix the auth bug in tribo" # natural-language work order
20
+ # ...PR opens → review → rework → merge, all visible in Telegram.
21
+ ```
22
+
23
+ **Surfaces**: CLI · [GitHub Action](action.yml) · [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=seunghunbae-3svs.solo-cto-agent) · Telegram bot · optional Discord mirror.
17
24
 
18
25
  > **Languages**: English (primary) - [한국어 안내](#한국어-안내) below.
19
26
 
20
- ## Quickstart (5 minutes)
27
+ ## Quickstart
21
28
 
22
29
  ```bash
23
- # 1. Install
30
+ # 1. Install + wizard (wizard auto-discovers your repos via gh CLI,
31
+ # creates the orchestrator repo on GitHub, sets TRACKED_REPOS,
32
+ # and installs workflows on every product repo you pick.)
24
33
  npm install -g solo-cto-agent
25
-
26
- # 2. Initialize (recommended: choose mode during wizard)
27
34
  npx solo-cto-agent init --wizard
28
35
 
29
- # 3. Set your Anthropic API key (required for reviews)
30
- # Get one at: https://console.anthropic.com/settings/keys
31
- export ANTHROPIC_API_KEY="sk-ant-..."
36
+ # 2. Keys (all set in your shell, then copied to repo secrets by setup)
37
+ export ANTHROPIC_API_KEY="sk-ant-..." # required Claude review + NL orders
38
+ export OPENAI_API_KEY="sk-..." # required for dual-agent / CTO tier
39
+ export ORCHESTRATOR_PAT="ghp_..." # required — cross-repo dispatch + write
40
+ export TELEGRAM_BOT_TOKEN="..." # optional — PR notifications + /commands
41
+ export TELEGRAM_CHAT_ID="..." # optional
42
+ export DISCORD_WEBHOOK_URL="https://..." # optional — mirror of Telegram output
43
+ export VERCEL_TOKEN="..." # optional — before/after visual reports
32
44
 
33
- # 4. (Optional) Set OpenAI key for dual-review mode
34
- # Get one at: https://platform.openai.com/api-keys
35
- export OPENAI_API_KEY="sk-..."
45
+ # 3. Run setup-pipeline (reads saved wizard selection; no manual --repos needed)
46
+ solo-cto-agent setup-pipeline --org <your-github-org>
36
47
 
37
- # 5. Verify everything is ready
38
- solo-cto-agent doctor --quick
48
+ # 4. Verify
49
+ solo-cto-agent doctor
39
50
 
40
- # 6. Run your first review (inside a git repo with staged changes)
41
- solo-cto-agent review
51
+ # 5. Kick off a real work order
52
+ solo-cto-agent do "add a monthly ARPU chart to the tribo admin dashboard"
53
+ # → LLM picks target repo, drafts a spec issue, labels agent-claude
54
+ # → claude worker opens a PR
55
+ # → cross-reviewer.js runs 3-round consensus
56
+ # → rework-agent.js pushes fixes if needed
57
+ # → visual-report.yml posts before/after screenshots
58
+ # → combined-pr-gate.yml sends "all checks passed" to Telegram
59
+ # → auto-merge-when-ready label makes GitHub merge on CI green
60
+ # → pr-merge-notify.yml sends final "✅ merged" to Telegram/Discord
42
61
  ```
43
62
 
44
- That is it. `doctor --quick` will tell you what is missing, where to get it, and the next command to run.
63
+ Every step above ships end-to-end today. The `doctor` subcommand tells you anything missing with the exact command to run.
64
+
65
+ ### Telegram bot — the phone-first control surface
66
+
67
+ After you set `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID`, the bot gives you:
68
+
69
+ | Command | What it does |
70
+ |---|---|
71
+ | `/status [repo]` | Open, non-draft PRs + review state across tracked repos |
72
+ | `/list [repo]` | Last 10 PRs, one-line summary each |
73
+ | `/rework <pr>` | Force a rework cycle on an existing PR |
74
+ | `/approve <pr>` | Approve the PR |
75
+ | `/do "<instruction>"` | Natural-language work order (same as CLI `do`) |
76
+ | `/digest` | Today's PR activity summary |
77
+ | `/merge <pr>` | Immediate merge (admin-only: `TELEGRAM_ADMIN_CHAT_IDS`) |
78
+
79
+ Every review / rework / visual-report message includes inline buttons — ✅ Approve · ❌ Reject · 🔧 Rework · 🔀 Merge. Tap to act without leaving Telegram.
45
80
 
46
- ### Platform-specific setup
81
+ If `DISCORD_WEBHOOK_URL` is set, visual-change screenshots and auto-diagnose reports mirror to Discord as attachments.
82
+
83
+ ### External services
84
+
85
+ | Service | Used for | Required? | Setup |
86
+ |---|---|---|---|
87
+ | **GitHub** | orchestrator repo + product repo workflows | ✅ required | wizard auto-creates orchestrator repo via `gh repo create` |
88
+ | **Anthropic** | Claude consensus review, NL orders, rework | ✅ required | `ANTHROPIC_API_KEY` env var |
89
+ | **OpenAI** | Codex counter-review (dual-agent) | CTO tier | `OPENAI_API_KEY` env var |
90
+ | **Vercel** | preview URLs for before/after visual-report | optional | `VERCEL_TOKEN` + `VERCEL_PROJECT_ID`. Works with **Netlify / Cloudflare Pages / Render / Railway previews** too if their `deployment_status` webhooks fire — the visual stage resolves the URL from SHA and shoots whichever host serves it. |
91
+ | **Telegram** | notifications + CTO commands | optional | `/telegram wizard` command + bot token |
92
+ | **Discord** | optional mirror of Telegram output | optional | `DISCORD_WEBHOOK_URL` on orchestrator secrets |
93
+ | **Browserless** | alternate screenshot provider (skips Playwright install cost) | optional | `VISUAL_REVIEW_PROVIDER=browserless` + `BROWSERLESS_API_KEY` |
94
+
95
+ ### Compatibility
96
+
97
+ - **Stack-agnostic.** The toolkit never touches your application code directly. Agents produce patches that land on your PR branch; your repo's existing CI / build tools verify them. Works with Next.js, Vite, Remix, SvelteKit, FastAPI, Rails — anything with a PR workflow.
98
+ - **Hosting-agnostic.** Vercel is the default for the visual-report preview URL resolver, but any host that ships preview URLs tied to commit SHAs works. For hosts without that (plain Docker, bare-metal, self-hosted): set `VISUAL_REVIEW_PROVIDER=off` and the pipeline just skips the visual stage — everything else still runs.
99
+ - **Database-agnostic.** The toolkit doesn't read or write your database. Postgres (Supabase / Neon / PlanetScale-Postgres), MySQL, SQLite, MongoDB — all fine. Agent workers DO see your schema files if they're in the repo (prisma/schema.prisma, supabase/schema.sql, etc.) so suggested fixes can be schema-aware.
100
+ - **Docker.** If your product repo is dockerized, nothing changes — GitHub Actions runners handle the build per your existing Dockerfile. The agents commit to the PR branch, your CI rebuilds the container, the auto-merge gate waits on that CI.
101
+ - **Windows / macOS / Linux** for the CLI. GitHub Actions runners are Linux for all automation paths.
102
+
103
+ ### Platform-specific CLI setup
47
104
 
48
105
  **macOS / Linux**
49
106
 
@@ -64,6 +121,8 @@ solo-cto-agent doctor
64
121
  If you choose `codex-main` during the wizard, also install:
65
122
  - GitHub CLI: [cli.github.com](https://cli.github.com/)
66
123
  - GitHub PAT for cross-repo dispatch: [github.com/settings/personal-access-tokens/new](https://github.com/settings/personal-access-tokens/new)
124
+ - **Classic PAT**: check `repo` + `workflow` scopes.
125
+ - **Fine-grained PAT**: grant `Contents: write`, `Issues: write`, `Pull requests: write`, `Actions: write` on every product repo listed in `setup-pipeline --repos`. The orchestrator pushes fix commits and posts comments on those repos on your behalf.
67
126
 
68
127
  If you choose `codex-main`, template drift audit is enabled by default:
69
128
  - local check: `solo-cto-agent template-audit`
@@ -259,18 +318,26 @@ solo-cto-agent/
259
318
 
260
319
  ## Three Axes: Tier / Agent / Mode
261
320
 
321
+ At a glance:
322
+
323
+ | | Cowork (semi-auto) | Codex (full-auto) |
324
+ |----------|--------------------|-----------------------|
325
+ | Builder | local + manual review | CI dispatch + auto-fix |
326
+ | CTO | local + dual-agent cross-review | CI dispatch + dual + cross-review + scoring |
327
+
328
+ Cowork runs in your terminal with you in the loop. Codex runs in CI and reworks itself until the PR passes.
329
+
262
330
  `solo-cto-agent` is configured across three independent axes. You choose each based on your workflow.
263
331
 
264
332
  | Axis | Decision | Options |
265
333
  |---|---|---|
266
- | Tier | Scope of capability | Maker / Builder / CTO |
334
+ | Tier | Scope of capability | Builder / CTO |
267
335
  | Agent | Who reviews | Cowork (Claude) / Cowork + Codex |
268
336
  | Mode | Automation depth | Semi-auto (cowork-main) / Full-auto (codex-main) |
269
337
 
270
338
  Quick pick if you are unsure:
271
- - Start with Maker + Cowork + Semi-auto.
272
- - Move to Builder when you are shipping real features.
273
- - Move to CTO + Full-auto when you want always-on CI/CD and multi-agent routing.
339
+ - Start with Builder + Cowork (single Claude agent, semi-auto, optional Telegram bot).
340
+ - Move to CTO + Full-auto when you want dual-agent cross-review and always-on CI/CD across repos.
274
341
 
275
342
  ### Agents (summary)
276
343
 
@@ -307,15 +374,13 @@ Full-auto adds:
307
374
  ### Tiers (summary)
308
375
 
309
376
  **Not sure which tier? One question:**
310
- - Are you shipping code to production? → **Builder** (default, recommended for most users)
311
- - Only doing idea validation / reviews? → **Maker**
312
- - Running multi-repo CI/CD with full automation? → **CTO**
377
+ - Solo dev shipping code with one Claude agent? → **Builder** (default, recommended for most users)
378
+ - Want dual-agent cross-review (Claude + Codex) and multi-repo CI/CD? → **CTO**
313
379
 
314
- | Tier | Includes | Recommended for |
315
- |---|---|---|
316
- | Maker | spark + review + memory + craft | idea and validation loops |
317
- | Builder | Maker + build + ship | solo dev shipping |
318
- | CTO | Builder + orchestrate | multi-agent + routing |
380
+ | Tier | Includes | Agents | Extras | Recommended for |
381
+ |---|---|---|---|---|
382
+ | Builder | spark + review + memory + craft + build + ship | solo Claude | optional Telegram bot for PR notify/approve | solo dev shipping |
383
+ | CTO | Builder + orchestrate | Claude + Codex (dual-agent cross-review) | agent scoring, routing, decision queue, daily briefing | multi-agent CI/CD across repos |
319
384
 
320
385
  Details: `docs/tier-matrix.md`, `docs/tier-examples.md`, `docs/cto-policy.md`, `docs/cowork-main-install.md`, `docs/configuration.md`.
321
386
 
package/bin/cli.js CHANGED
@@ -30,6 +30,8 @@ let telegramBot;
30
30
  try { telegramBot = require("./telegram-bot"); } catch (_) { telegramBot = null; }
31
31
  let selfEvolve;
32
32
  try { selfEvolve = require("./self-evolve"); } catch (_) { selfEvolve = null; }
33
+ let repoDiscovery;
34
+ try { repoDiscovery = require("./repo-discovery"); } catch (_) { repoDiscovery = null; }
33
35
 
34
36
  const ROOT = path.resolve(__dirname, "..");
35
37
  const DEFAULT_CATALOG = path.join(ROOT, "failure-catalog.json");
@@ -65,6 +67,8 @@ function printHelp() {
65
67
  Usage:
66
68
  solo-cto-agent init [--force] [--preset maker|builder|cto] [--wizard]
67
69
  solo-cto-agent setup-pipeline --org <github-org> [--tier builder|cto] [--repos <repo1,repo2,...>]
70
+ solo-cto-agent repos list [--org <github-org>] # show/re-pick the saved repo selection
71
+ solo-cto-agent do "<instruction>" [--dry-run] [--repo owner/name] [--agent claude|codex]
68
72
  solo-cto-agent setup-repo <repo-path> --org <github-org> [--tier builder|cto]
69
73
  solo-cto-agent auto-setup # Install solo-cto-pipeline.yml to your repos (centralized)
70
74
  solo-cto-agent setup --central --org <owner> [--orchestrator <repo>] [--repos <r1,r2,...>] [--dry-run]
@@ -93,6 +97,8 @@ Usage:
93
97
  Commands:
94
98
  init Install skills to ~/.claude/skills/, then run doctor to verify setup
95
99
  setup-pipeline Full pipeline setup: create orchestrator repo + install workflows to product repos
100
+ repos list Print current saved repo selection (from init wizard) and re-pick interactively
101
+ do Natural-language work order: LLM parses intent → creates labeled issue → worker runs
96
102
  setup-repo Install dual-agent workflows to a single product repo
97
103
  auto-setup Install solo-cto-pipeline.yml (centralized thin workflow) to selected repos
98
104
  setup --central Centralize cross-repo workflows (digest, bot-runner) to orchestrator repo
@@ -401,6 +407,82 @@ Style: {{YOUR_STYLE}}
401
407
  doctorCommand({ exitOnError: false, quick: true });
402
408
  }
403
409
 
410
+ // ─── repos: show/re-pick saved selection ────────────────────
411
+
412
+ async function reposCommand(args) {
413
+ if (!repoDiscovery) {
414
+ console.error("❌ repo-discovery module not available in this install.");
415
+ process.exit(1);
416
+ }
417
+ const sub = args[1] || "list";
418
+ if (sub !== "list") {
419
+ console.error(`Unknown repos subcommand: ${sub}`);
420
+ console.error("Usage: solo-cto-agent repos list [--org <github-org>]");
421
+ process.exit(1);
422
+ }
423
+
424
+ const orgIndex = args.indexOf("--org");
425
+ let org = orgIndex >= 0 ? args[orgIndex + 1] : null;
426
+
427
+ const saved = repoDiscovery.loadSelection();
428
+ if (saved) {
429
+ console.log(`Saved selection (${repoDiscovery.selectionPath()}):`);
430
+ console.log(` org: ${saved.org || "(user-scoped)"}`);
431
+ console.log(` updated: ${saved.updatedAt || "—"}`);
432
+ console.log(` repos: ${saved.selected.length ? saved.selected.join(", ") : "(none)"}`);
433
+ if (!org && saved.org) org = saved.org;
434
+ } else {
435
+ console.log("No saved selection yet. Run `solo-cto-agent init --wizard` first, or re-pick below.");
436
+ }
437
+
438
+ // Non-TTY callers (CI) just get the print-out.
439
+ const { isTTY, createRl } = require("./prompt-utils");
440
+ const { ask } = require("./prompt-utils");
441
+ if (!isTTY()) {
442
+ console.log("\nℹ️ Non-interactive terminal — skipping re-pick prompt.");
443
+ return;
444
+ }
445
+
446
+ const rl = createRl();
447
+ try {
448
+ const again = await ask(rl, "\nRe-pick repos now?", "n");
449
+ if (!/^y(es)?$/i.test(again.trim())) {
450
+ rl.close();
451
+ return;
452
+ }
453
+
454
+ let repos = null;
455
+ try {
456
+ repos = repoDiscovery.fetchRepos({ org });
457
+ } catch (err) {
458
+ console.log(`⚠️ ${err.message}`);
459
+ }
460
+
461
+ if (repos == null) {
462
+ console.log("`gh` CLI not found. Install from https://cli.github.com/ then `gh auth login`.");
463
+ const manual = await ask(rl, "Paste repo slugs manually (comma-separated, or blank to cancel)", "");
464
+ const selected = manual.split(",").map((s) => s.trim()).filter(Boolean);
465
+ if (selected.length) {
466
+ const file = repoDiscovery.saveSelection({ org, selected, discovered: [] });
467
+ console.log(`✅ Saved ${selected.length} repo(s) to ${file}`);
468
+ } else {
469
+ console.log("No changes.");
470
+ }
471
+ } else if (repos.length === 0) {
472
+ console.log("No repositories returned from gh.");
473
+ } else {
474
+ const preselected = saved && Array.isArray(saved.selected) && saved.selected.length
475
+ ? saved.selected
476
+ : repoDiscovery.defaultPreselect(repos);
477
+ const selected = await repoDiscovery.pickReposInteractive(rl, ask, repos, preselected);
478
+ const file = repoDiscovery.saveSelection({ org, selected, discovered: repos });
479
+ console.log(`✅ Saved ${selected.length} repo(s) to ${file}`);
480
+ }
481
+ } finally {
482
+ rl.close();
483
+ }
484
+ }
485
+
404
486
  // ─── setup-pipeline: Full Pipeline Deploy ───────────────────
405
487
 
406
488
  function setupPipelineCommand(tier, org, repos, orchName, force) {
@@ -2061,6 +2143,17 @@ async function main() {
2061
2143
  return false;
2062
2144
  }
2063
2145
 
2146
+ if (cmd === "repos") {
2147
+ await reposCommand(args);
2148
+ return;
2149
+ }
2150
+
2151
+ if (cmd === "do") {
2152
+ const doModule = require("./do");
2153
+ await doModule.main();
2154
+ return;
2155
+ }
2156
+
2064
2157
  if (cmd === "setup-pipeline") {
2065
2158
  if (checkCoworkMainMode()) {
2066
2159
  console.log("ℹ️ Not needed in cowork-main mode. Use `review`, `knowledge`, and `sync` commands instead.");
@@ -2071,7 +2164,15 @@ async function main() {
2071
2164
  const orgIndex = args.indexOf("--org");
2072
2165
  const org = orgIndex >= 0 ? args[orgIndex + 1] : null;
2073
2166
  const reposIndex = args.indexOf("--repos");
2074
- const repos = reposIndex >= 0 ? args[reposIndex + 1] : null;
2167
+ let repos = reposIndex >= 0 ? args[reposIndex + 1] : null;
2168
+ // Fall back to the selection persisted by `init --wizard` / `repos list`.
2169
+ if (!repos && repoDiscovery) {
2170
+ const saved = repoDiscovery.loadSelection();
2171
+ if (saved && Array.isArray(saved.selected) && saved.selected.length) {
2172
+ repos = saved.selected.join(",");
2173
+ console.log(`ℹ️ Using saved repo selection (${saved.selected.length} repos from ${repoDiscovery.selectionPath()}).`);
2174
+ }
2175
+ }
2075
2176
  const orchIndex = args.indexOf("--orchestrator-name");
2076
2177
  const orchName = orchIndex >= 0 ? args[orchIndex + 1] : null;
2077
2178
  setupPipelineCommand(tier, org, repos, orchName, force);
@@ -2136,7 +2237,14 @@ async function main() {
2136
2237
  const orgIndex = args.indexOf("--org");
2137
2238
  const org = orgIndex >= 0 ? args[orgIndex + 1] : null;
2138
2239
  const reposIndex = args.indexOf("--repos");
2139
- const repos = reposIndex >= 0 ? args[reposIndex + 1] : null;
2240
+ let repos = reposIndex >= 0 ? args[reposIndex + 1] : null;
2241
+ if (!repos && repoDiscovery) {
2242
+ const saved = repoDiscovery.loadSelection();
2243
+ if (saved && Array.isArray(saved.selected) && saved.selected.length) {
2244
+ repos = saved.selected.join(",");
2245
+ console.log(`ℹ️ Using saved repo selection (${saved.selected.length} repos).`);
2246
+ }
2247
+ }
2140
2248
  const orchIndex = args.indexOf("--orchestrator-name");
2141
2249
  const orchName = orchIndex >= 0 ? args[orchIndex + 1] : null;
2142
2250
  upgradeCommand(org, repos, orchName);
@@ -2147,7 +2255,14 @@ async function main() {
2147
2255
  const orgIndex = args.indexOf("--org");
2148
2256
  const org = orgIndex >= 0 ? args[orgIndex + 1] : null;
2149
2257
  const reposIndex = args.indexOf("--repos");
2150
- const repos = reposIndex >= 0 ? args[reposIndex + 1] : null;
2258
+ let repos = reposIndex >= 0 ? args[reposIndex + 1] : null;
2259
+ if (!repos && repoDiscovery) {
2260
+ const saved = repoDiscovery.loadSelection();
2261
+ if (saved && Array.isArray(saved.selected) && saved.selected.length) {
2262
+ repos = saved.selected.join(",");
2263
+ console.log(`ℹ️ Using saved repo selection (${saved.selected.length} repos).`);
2264
+ }
2265
+ }
2151
2266
  const orchIndex = args.indexOf("--orchestrator-name");
2152
2267
  const orchName = orchIndex >= 0 ? args[orchIndex + 1] : "dual-agent-orchestrator";
2153
2268
  const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.ORCHESTRATOR_PAT;
package/bin/do.js ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * do — natural-language work order entry point.
5
+ *
6
+ * Usage:
7
+ * solo-cto-agent do "fix the login bug in tribo"
8
+ * solo-cto-agent do "improve the hero section typography on the landing page"
9
+ *
10
+ * What it does:
11
+ * 1. Loads tracked repos from the saved wizard selection (bin/repo-discovery).
12
+ * 2. Asks Claude to translate the NL order into a structured issue spec.
13
+ * 3. Creates the issue on the target product repo with an `agent-{claude|codex}`
14
+ * label so existing orchestrator workflows pick it up and run the real
15
+ * implementation agent.
16
+ *
17
+ * Environment:
18
+ * ANTHROPIC_API_KEY required (for intent parsing)
19
+ * GITHUB_TOKEN required (to create the issue)
20
+ *
21
+ * We DO NOT write code here. The code is written by the implementing
22
+ * worker (claude-auto.yml / codex-auto.yml) once the labeled issue lands.
23
+ */
24
+
25
+ "use strict";
26
+
27
+ const { parseIntent, dispatchOrder } = require("./lib/nl-orchestrator");
28
+ let repoDiscovery;
29
+ try {
30
+ repoDiscovery = require("./repo-discovery");
31
+ } catch (_) {
32
+ repoDiscovery = null;
33
+ }
34
+
35
+ function printHelp() {
36
+ console.log(`do — issue a natural-language work order
37
+
38
+ Usage:
39
+ solo-cto-agent do "<natural language instruction>"
40
+
41
+ Options:
42
+ --dry-run Print the parsed intent without creating an issue
43
+ --repo owner/name Override auto-selected target repo
44
+ --agent claude|codex Force a specific implementer (default: LLM decides)
45
+ --help, -h Show this
46
+
47
+ Setup:
48
+ - Run \`solo-cto-agent init --wizard\` to discover and save tracked repos.
49
+ - Set ANTHROPIC_API_KEY (for intent parsing).
50
+ - Set GITHUB_TOKEN (for issue creation).
51
+
52
+ Examples:
53
+ solo-cto-agent do "fix the staging deploy error I saw in tribo"
54
+ solo-cto-agent do "redesign the login hero on ohmywork — cleaner, less gradient" --agent claude
55
+ solo-cto-agent do "add unit tests for the ARPU calculator in tribo" --agent codex
56
+ `);
57
+ }
58
+
59
+ function parseArgs(argv) {
60
+ const out = { text: null, dryRun: false, repo: null, agent: null, help: false };
61
+ const rest = [];
62
+ for (let i = 0; i < argv.length; i++) {
63
+ const a = argv[i];
64
+ if (a === "--help" || a === "-h") out.help = true;
65
+ else if (a === "--dry-run") out.dryRun = true;
66
+ else if (a === "--repo" && argv[i + 1]) {
67
+ out.repo = argv[++i];
68
+ } else if (a === "--agent" && argv[i + 1]) {
69
+ out.agent = argv[++i];
70
+ } else {
71
+ rest.push(a);
72
+ }
73
+ }
74
+ out.text = rest.join(" ").trim();
75
+ return out;
76
+ }
77
+
78
+ async function main() {
79
+ const args = parseArgs(process.argv.slice(3)); // slice 3: node, cli.js, "do"
80
+ if (args.help || !args.text) {
81
+ printHelp();
82
+ process.exit(args.help ? 0 : 1);
83
+ }
84
+
85
+ // 1. Tracked repos
86
+ if (!repoDiscovery) {
87
+ console.error("❌ repo-discovery module missing — reinstall solo-cto-agent.");
88
+ process.exit(1);
89
+ }
90
+ const saved = repoDiscovery.loadSelection();
91
+ if (!saved || !Array.isArray(saved.discovered) || saved.discovered.length === 0) {
92
+ console.error("❌ No tracked repos saved. Run `solo-cto-agent init --wizard` first.");
93
+ process.exit(1);
94
+ }
95
+ // If user selected a subset, only consider those; otherwise all discovered.
96
+ const selectedSet = new Set(saved.selected || []);
97
+ const trackedRepos = saved.discovered.filter((r) =>
98
+ selectedSet.size === 0 ? true : selectedSet.has(r.name)
99
+ );
100
+
101
+ if (args.repo) {
102
+ // --repo override: accept only if in the tracked list
103
+ const hit = trackedRepos.find((r) => (r.fullName || r.name) === args.repo);
104
+ if (!hit) {
105
+ console.error(`❌ --repo ${args.repo} is not in your tracked repo list.`);
106
+ console.error(` Tracked: ${trackedRepos.map((r) => r.fullName || r.name).join(", ")}`);
107
+ process.exit(1);
108
+ }
109
+ }
110
+
111
+ // 2. Anthropic client — thin fetch shim so solo-cto-agent keeps zero
112
+ // runtime deps. The orchestrator worker uses the real SDK (installed
113
+ // by its workflow) but the CLI doesn't need it.
114
+ if (!process.env.ANTHROPIC_API_KEY) {
115
+ console.error("❌ ANTHROPIC_API_KEY not set. Needed to parse natural-language intent.");
116
+ process.exit(1);
117
+ }
118
+ const anthropicClient = buildAnthropicFetchClient(process.env.ANTHROPIC_API_KEY);
119
+
120
+ // 3. GitHub client (shell out to gh CLI to keep deps small). We wrap the
121
+ // ghApi shape the nl-orchestrator expects around gh.
122
+ const ghApi = buildGhApi(args.dryRun);
123
+
124
+ // 4. Run
125
+ try {
126
+ const intent = await parseIntent({ userText: args.text, trackedRepos, anthropicClient });
127
+ if (args.repo) intent.repo = args.repo;
128
+ if (args.agent) intent.agent = args.agent;
129
+
130
+ if (args.dryRun) {
131
+ console.log(JSON.stringify(intent, null, 2));
132
+ console.log("\n(dry-run — no issue created)");
133
+ return;
134
+ }
135
+
136
+ const result = await dispatchOrder({ intent, ghApi });
137
+ console.log(`✅ Issue created: ${result.issueUrl}`);
138
+ console.log(` Repo: ${result.repo}`);
139
+ console.log(` Agent: ${result.agent}`);
140
+ console.log(` Scope: ${result.scope}`);
141
+ console.log(` Labels: ${result.labels.join(", ")}`);
142
+ console.log(`\nThe '${result.agent}-auto' workflow will pick this up and open a PR.`);
143
+ } catch (err) {
144
+ console.error(`❌ ${err.message}`);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Thin Octokit-shaped client that only implements the methods nl-orchestrator
151
+ * actually calls. Uses `gh api` so we don't pull in @octokit/rest at runtime.
152
+ * Set dry=true to skip writes entirely (returns a stub).
153
+ */
154
+ function buildGhApi(dry) {
155
+ const { execFileSync } = require("child_process");
156
+ return {
157
+ issues: {
158
+ create: async ({ owner, repo, title, body, labels }) => {
159
+ if (dry) {
160
+ return { data: { html_url: "(dry-run)", number: 0 } };
161
+ }
162
+ const payload = JSON.stringify({ title, body, labels });
163
+ const out = execFileSync("gh", ["api", "-X", "POST", `/repos/${owner}/${repo}/issues`, "--input", "-"], {
164
+ input: payload,
165
+ encoding: "utf8",
166
+ stdio: ["pipe", "pipe", "pipe"],
167
+ });
168
+ const data = JSON.parse(out);
169
+ return { data };
170
+ },
171
+ },
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Anthropic client shim with just the `.messages.create` method the
177
+ * nl-orchestrator expects, calling the public REST endpoint directly.
178
+ * Keeps the CLI free of `@anthropic-ai/sdk` as a runtime dep.
179
+ */
180
+ function buildAnthropicFetchClient(apiKey) {
181
+ return {
182
+ messages: {
183
+ create: async ({ model, max_tokens, temperature, system, messages }) => {
184
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
185
+ method: "POST",
186
+ headers: {
187
+ "x-api-key": apiKey,
188
+ "anthropic-version": "2023-06-01",
189
+ "content-type": "application/json",
190
+ },
191
+ body: JSON.stringify({ model, max_tokens, temperature, system, messages }),
192
+ });
193
+ if (!res.ok) {
194
+ const body = await res.text();
195
+ throw new Error(`Anthropic ${res.status}: ${body.slice(0, 300)}`);
196
+ }
197
+ return res.json();
198
+ },
199
+ },
200
+ };
201
+ }
202
+
203
+ module.exports = { main };
204
+
205
+ if (require.main === module) {
206
+ main().catch((e) => {
207
+ console.error(e.message);
208
+ process.exit(1);
209
+ });
210
+ }