solo-cto-agent 1.2.0 → 1.3.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 (54) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/action.yml +101 -0
  3. package/bin/central-setup.js +352 -0
  4. package/bin/cli.js +356 -17
  5. package/bin/cowork-engine.js +187 -1382
  6. package/bin/diff-guard.js +159 -0
  7. package/bin/engine/core.js +326 -0
  8. package/bin/engine/review.js +406 -0
  9. package/bin/engine/routine.js +288 -0
  10. package/bin/engine/session.js +323 -0
  11. package/bin/external-signals.js +3 -3
  12. package/bin/notify.js +15 -1
  13. package/bin/plugin-manager.js +195 -0
  14. package/bin/rework.js +91 -13
  15. package/bin/safe-log.js +97 -0
  16. package/bin/self-evolve/error-collector.js +2 -2
  17. package/bin/self-evolve/external-trends.js +5 -5
  18. package/bin/self-evolve/feedback-collector.js +1 -1
  19. package/bin/self-evolve/quality-analyzer.js +3 -3
  20. package/bin/self-evolve/self-evolve-orchestrator.js +1 -1
  21. package/bin/self-evolve/skill-improver.js +3 -3
  22. package/bin/self-evolve/skill-scout.js +7 -7
  23. package/bin/self-evolve/weekly-report.js +3 -3
  24. package/bin/telegram-bot.js +561 -0
  25. package/bin/telegram-wizard.js +0 -6
  26. package/bin/template-audit.js +104 -0
  27. package/bin/watch.js +2 -2
  28. package/docs/cursor.md +310 -0
  29. package/docs/demo.svg +1 -1
  30. package/docs/migration-v1.3.md +145 -0
  31. package/docs/windsurf.md +475 -0
  32. package/failure-catalog.json +71 -1
  33. package/index.d.ts +843 -0
  34. package/package.json +4 -2
  35. package/templates/central/.github/workflows/telegram-bot-runner.yml +56 -0
  36. package/templates/central/.github/workflows/telegram-digest.yml +114 -0
  37. package/templates/orchestrator/CLAUDE.md +75 -0
  38. package/templates/orchestrator/api/telegram-webhook.js +16 -16
  39. package/templates/orchestrator/ops/agents/claude-reviewer.js +1 -1
  40. package/templates/orchestrator/ops/agents/claude-worker.js +5 -5
  41. package/templates/orchestrator/ops/agents/codex-worker.js +3 -3
  42. package/templates/orchestrator/ops/agents/cross-reviewer.js +2 -1
  43. package/templates/orchestrator/ops/agents/status-checker.js +3 -3
  44. package/templates/orchestrator/ops/config/design-guidelines.json +4 -4
  45. package/templates/orchestrator/ops/orchestrator/decision-message.js +6 -3
  46. package/templates/orchestrator/ops/scripts/auto-diagnose.js +7 -7
  47. package/templates/orchestrator/ops/scripts/daily-briefing.js +5 -5
  48. package/templates/orchestrator/ops/scripts/decision-queue.js +3 -3
  49. package/templates/orchestrator/ops/scripts/meta-report.js +3 -3
  50. package/templates/product-repo/.claude/hooks/post-compact.sh +117 -0
  51. package/templates/product-repo/.claude/settings.json +22 -0
  52. package/templates/product-repo/CLAUDE.md +184 -0
  53. package/templates/workflows/solo-cto-review.yml +1 -1
  54. package/bin/consensus-review.js +0 -962
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.3.0 (2026-04-17)
4
+
5
+ **Theme**: Tier 3 deep integration features — plugin registry search, setup automation, type system enhancements.
6
+
7
+ ### Highlights
8
+ * `solo-cto-agent plugin search <query>` — search npm registry for plugins with "solo-cto-agent-plugin" keyword
9
+ * setup.sh enhancements: `--include-benchmarks` flag to deploy dashboard.html, auto-detection of Cursor/Windsurf editors
10
+ * Auto-copy of editor-specific docs (docs/cursor.md, docs/windsurf.md) based on detected environment
11
+ * Enhanced TypeScript definitions: BenchmarkMetrics, BenchmarkDiffResult, PluginSearchResult, HistoryEntry
12
+ * Full test coverage for plugin search, CLI subcommands, and new features
13
+
14
+ ### New: Plugin Search
15
+ * `solo-cto-agent plugin search <query>` fetches from npm registry with fallback to helpful error messages
16
+ * `--json` flag for programmatic consumption
17
+ * Graceful handling of network failures with user-friendly messages
18
+ * Integration with plugin-manager.js for unified plugin experience
19
+
20
+ ### New: Setup.sh Enhancements
21
+ * `--include-benchmarks` flag copies benchmarks/dashboard.html to orchestrator directory
22
+ * Auto-detection of Cursor editor (checks ~/.cursor/ directory)
23
+ * Auto-detection of Windsurf editor (checks ~/.windsurf/ directory)
24
+ * Automatic copy of editor-specific documentation to CLAUDE_DIR/docs/
25
+ * Enhanced summary output showing detected editor and installed features
26
+
27
+ ### Enhanced Types
28
+ * `BenchmarkMetrics`: Matches metrics-latest.json shape (name, description, value, unit, timestamp)
29
+ * `BenchmarkDiffResult`: For --diff output (baseline, current, delta, percentChange)
30
+ * `PluginSearchResult`: npm registry search results (name, version, description, author, links)
31
+ * `HistoryEntry`: For benchmarks/history/*.json (timestamp, metrics[], changes[])
32
+
33
+ ### Tests
34
+ * `tests/plugin-search.test.mjs` — 10 new tests for registry search with HTTP mocking
35
+ * Updated CLI tests for `plugin search` and `plugin list` subcommands
36
+ * All existing tests pass with version bump
37
+
38
+ ---
39
+
3
40
  ## v1.2.0 (2026-04-17)
4
41
 
5
42
  **Theme**: Public release polish + cowork-main Phase 2/3 + dual-agent metrics.
@@ -82,7 +119,41 @@ non-interactive verify in CI, and tear it all down with one command.
82
119
 
83
120
  ---
84
121
 
85
- ## Unreleased — Toolkit upgrade: per-tool entry points + examples/
122
+ ## Unreleased
123
+
124
+ * fix: routine.js readTier import from personalization (not core)
125
+
126
+ * fix: resolve 2 hanging tests + add vitest timeout config
127
+
128
+ * feat: P3 — GitHub Actions marketplace, VS Code extension, npm release prep
129
+
130
+ * feat: P2 — type sync, template validation CI, migration guide
131
+
132
+ * feat: P1 — cowork-engine split, plugin install, template-audit --apply
133
+
134
+ * security: add API key masking + diff secret detection (P0)
135
+
136
+ * feat: add `setup --central` for centralized workflow architecture
137
+
138
+ * feat(tier3): plugin registry search, setup.sh enhancement, v1.3.0 (#100)
139
+
140
+ * feat: Tier 2 — metrics history, benchmark diff/trend, enhanced validation (#99)
141
+
142
+ * feat: Tier 1 — benchmark CLI, docs, expanded error catalog (#98)
143
+
144
+ * feat: /prs, /dashboard commands + natural language (T3) (#104)
145
+
146
+ * fix: align HTTP timeout with Telegram long-poll timeout (#103)
147
+
148
+ * fix: delete webhook before getUpdates polling (#102)
149
+
150
+ * feat: telegram-bot callback handler + remove experimental gate (#101)
151
+
152
+ * fix: create local ref for base branch in solo-cto-review template
153
+
154
+ * chore: anonymize personal info and internal project names
155
+
156
+ * release: v1.2.0 — public release polish — Toolkit upgrade: per-tool entry points + examples/
86
157
 
87
158
  **Theme**: repositioning from "skill pack" to "toolkit" by splitting the
88
159
  docs surface along tool boundaries and filling `examples/` with real
package/action.yml ADDED
@@ -0,0 +1,101 @@
1
+ name: "Solo CTO Agent — AI Code Review"
2
+ description: "CTO-level AI code review with dual-agent cross-check (Claude + OpenAI), secret detection, and circuit breakers."
3
+ author: "seunghunbae-3svs"
4
+
5
+ branding:
6
+ icon: "shield"
7
+ color: "purple"
8
+
9
+ inputs:
10
+ anthropic_api_key:
11
+ description: "Anthropic API key for Claude reviews"
12
+ required: true
13
+ openai_api_key:
14
+ description: "OpenAI API key for dual-agent cross-review (optional — enables Tier 2+)"
15
+ required: false
16
+ tier:
17
+ description: "Agent tier: maker | builder | cto"
18
+ required: false
19
+ default: "builder"
20
+ mode:
21
+ description: "Review mode: review | dual-review | deep-review"
22
+ required: false
23
+ default: "review"
24
+ target_branch:
25
+ description: "Base branch for diff comparison"
26
+ required: false
27
+ default: "main"
28
+ redact:
29
+ description: "Redact secrets from diff before sending to AI (true/false)"
30
+ required: false
31
+ default: "true"
32
+ fail_on:
33
+ description: "Fail the check if verdict is REQUEST_CHANGES or BLOCKER found (true/false)"
34
+ required: false
35
+ default: "false"
36
+ extra_args:
37
+ description: "Additional CLI arguments passed to solo-cto-agent"
38
+ required: false
39
+ default: ""
40
+
41
+ outputs:
42
+ verdict:
43
+ description: "Review verdict: APPROVE | REQUEST_CHANGES | COMMENT | UNKNOWN"
44
+ issues_count:
45
+ description: "Number of issues found"
46
+ summary:
47
+ description: "One-line review summary"
48
+
49
+ runs:
50
+ using: "composite"
51
+ steps:
52
+ - name: Setup Node.js
53
+ uses: actions/setup-node@v4
54
+ with:
55
+ node-version: "20"
56
+
57
+ - name: Install solo-cto-agent
58
+ shell: bash
59
+ run: npm install -g solo-cto-agent@latest
60
+
61
+ - name: Run review
62
+ id: review
63
+ shell: bash
64
+ env:
65
+ ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
66
+ OPENAI_API_KEY: ${{ inputs.openai_api_key }}
67
+ SOLO_CTO_TIER: ${{ inputs.tier }}
68
+ run: |
69
+ REVIEW_CMD="${{ inputs.mode }}"
70
+ ARGS="--branch --target ${{ inputs.target_branch }} --json"
71
+
72
+ if [ "${{ inputs.redact }}" = "true" ]; then
73
+ ARGS="$ARGS --redact"
74
+ fi
75
+
76
+ if [ -n "${{ inputs.extra_args }}" ]; then
77
+ ARGS="$ARGS ${{ inputs.extra_args }}"
78
+ fi
79
+
80
+ # Run review and capture output
81
+ OUTPUT=$(solo-cto-agent $REVIEW_CMD $ARGS 2>&1) || true
82
+ echo "$OUTPUT"
83
+
84
+ # Parse JSON output for verdict and issues count
85
+ VERDICT=$(echo "$OUTPUT" | grep -o '"verdict":"[^"]*"' | head -1 | cut -d'"' -f4)
86
+ ISSUES=$(echo "$OUTPUT" | grep -o '"issues":\[[^]]*\]' | head -1 | grep -o '"severity"' | wc -l)
87
+ SUMMARY=$(echo "$OUTPUT" | grep -o '"summary":"[^"]*"' | head -1 | cut -d'"' -f4)
88
+
89
+ echo "verdict=${VERDICT:-UNKNOWN}" >> $GITHUB_OUTPUT
90
+ echo "issues_count=${ISSUES:-0}" >> $GITHUB_OUTPUT
91
+ echo "summary=${SUMMARY:-No summary}" >> $GITHUB_OUTPUT
92
+
93
+ - name: Check fail condition
94
+ if: inputs.fail_on == 'true'
95
+ shell: bash
96
+ run: |
97
+ VERDICT="${{ steps.review.outputs.verdict }}"
98
+ if [ "$VERDICT" = "REQUEST_CHANGES" ]; then
99
+ echo "::error::Review verdict is REQUEST_CHANGES — failing check."
100
+ exit 1
101
+ fi
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ /**
4
+ * central-setup.js
5
+ *
6
+ * Sets up centralized workflow architecture:
7
+ * - Orchestrator repo: cross-repo workflows (telegram-bot-runner, telegram-digest)
8
+ * - Product repos: repo-local workflows only (solo-cto-review, telegram-notify, solo-cto-pipeline)
9
+ * - Disables cross-repo workflows if found in product repos (prevents notification spam)
10
+ *
11
+ * Usage:
12
+ * solo-cto-agent setup --central --org <owner> --orchestrator <repo> --repos <repo1,repo2,...>
13
+ * GITHUB_TOKEN=... solo-cto-agent setup --central
14
+ */
15
+
16
+ const https = require("https");
17
+ const fs = require("fs");
18
+ const path = require("path");
19
+
20
+ // ─── Constants ────────────────────────────────────
21
+
22
+ const CROSS_REPO_WORKFLOWS = ["telegram-bot-runner.yml", "telegram-digest.yml"];
23
+ const REPO_LOCAL_WORKFLOWS = [
24
+ "solo-cto-review.yml",
25
+ "solo-cto-pipeline.yml",
26
+ "telegram-notify.yml",
27
+ ];
28
+
29
+ const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
30
+
31
+ // ─── GitHub API Helper ─────────────────────────────
32
+
33
+ function githubRequest(method, reqPath, token, body = null) {
34
+ return new Promise((resolve, reject) => {
35
+ const options = {
36
+ hostname: "api.github.com",
37
+ port: 443,
38
+ path: reqPath,
39
+ method,
40
+ headers: {
41
+ "User-Agent": "solo-cto-agent/central-setup",
42
+ Authorization: `token ${token}`,
43
+ Accept: "application/vnd.github.v3+json",
44
+ },
45
+ };
46
+
47
+ if (body) {
48
+ const bodyStr = JSON.stringify(body);
49
+ options.headers["Content-Type"] = "application/json";
50
+ options.headers["Content-Length"] = Buffer.byteLength(bodyStr);
51
+ }
52
+
53
+ const req = https.request(options, (res) => {
54
+ let data = "";
55
+ res.on("data", (chunk) => (data += chunk));
56
+ res.on("end", () => {
57
+ try {
58
+ resolve({ status: res.statusCode, data: JSON.parse(data) });
59
+ } catch {
60
+ resolve({ status: res.statusCode, data });
61
+ }
62
+ });
63
+ });
64
+
65
+ req.on("error", reject);
66
+ if (body) req.write(JSON.stringify(body));
67
+ req.end();
68
+ });
69
+ }
70
+
71
+ // ─── Core Logic ────────────────────────────────────
72
+
73
+ /**
74
+ * Get workflow IDs for a repo, filtered by name list
75
+ */
76
+ async function getWorkflowsByName(token, owner, repo, nameFilter) {
77
+ const resp = await githubRequest(
78
+ "GET",
79
+ `/repos/${owner}/${repo}/actions/workflows`,
80
+ token
81
+ );
82
+ if (resp.status !== 200) return [];
83
+
84
+ return (resp.data.workflows || []).filter((w) => {
85
+ const filename = w.path.split("/").pop();
86
+ return nameFilter.includes(filename);
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Disable a workflow by ID
92
+ */
93
+ async function disableWorkflow(token, owner, repo, workflowId) {
94
+ const resp = await githubRequest(
95
+ "PUT",
96
+ `/repos/${owner}/${repo}/actions/workflows/${workflowId}/disable`,
97
+ token
98
+ );
99
+ // 204 = success, 403 = already disabled
100
+ return resp.status === 204 || resp.status === 403;
101
+ }
102
+
103
+ /**
104
+ * Enable a workflow by ID
105
+ */
106
+ async function enableWorkflow(token, owner, repo, workflowId) {
107
+ const resp = await githubRequest(
108
+ "PUT",
109
+ `/repos/${owner}/${repo}/actions/workflows/${workflowId}/enable`,
110
+ token
111
+ );
112
+ return resp.status === 204 || resp.status === 403;
113
+ }
114
+
115
+ /**
116
+ * Upload a workflow file to a repo via GitHub Contents API
117
+ */
118
+ async function uploadWorkflow(token, owner, repo, filename, content, message) {
119
+ const apiPath = `/repos/${owner}/${repo}/contents/.github/workflows/${filename}`;
120
+
121
+ // Check if file exists (need sha for update)
122
+ let sha = null;
123
+ const check = await githubRequest("GET", apiPath, token);
124
+ if (check.status === 200 && check.data.sha) {
125
+ sha = check.data.sha;
126
+ }
127
+
128
+ const body = {
129
+ message,
130
+ content: Buffer.from(content).toString("base64"),
131
+ };
132
+ if (sha) body.sha = sha;
133
+
134
+ const resp = await githubRequest("PUT", apiPath, token, body);
135
+ return resp.status === 200 || resp.status === 201;
136
+ }
137
+
138
+ /**
139
+ * Read a template file, replacing placeholders
140
+ */
141
+ function readTemplate(templatePath, replacements = {}) {
142
+ let content = fs.readFileSync(templatePath, "utf8");
143
+ for (const [key, value] of Object.entries(replacements)) {
144
+ content = content.replace(new RegExp(escapeRegExp(key), "g"), value);
145
+ }
146
+ return content;
147
+ }
148
+
149
+ function escapeRegExp(s) {
150
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
151
+ }
152
+
153
+ // ─── Main ──────────────────────────────────────────
154
+
155
+ async function centralSetup({
156
+ org,
157
+ orchestrator = "dual-agent-review-orchestrator",
158
+ repos = [],
159
+ token,
160
+ dryRun = false,
161
+ }) {
162
+ if (!token) {
163
+ console.error("❌ GITHUB_TOKEN required.");
164
+ process.exit(1);
165
+ }
166
+ if (!org) {
167
+ console.error("❌ --org required (your GitHub username or org).");
168
+ process.exit(1);
169
+ }
170
+
171
+ console.log("");
172
+ console.log("╔══════════════════════════════════════════════════╗");
173
+ console.log("║ solo-cto-agent — Central Control Setup ║");
174
+ console.log("╠══════════════════════════════════════════════════╣");
175
+ console.log(`║ Org: ${org.padEnd(34)}║`);
176
+ console.log(`║ Orchestrator: ${orchestrator.padEnd(34)}║`);
177
+ console.log(`║ Products: ${(repos.join(", ") || "(auto-detect)").padEnd(34).slice(0, 34)}║`);
178
+ console.log("╚══════════════════════════════════════════════════╝");
179
+ console.log("");
180
+
181
+ const results = { orchestrator: [], products: [], disabled: [] };
182
+
183
+ // ──────────────────────────────────────────────
184
+ // Step 1: Orchestrator — ensure cross-repo workflows exist + active
185
+ // ──────────────────────────────────────────────
186
+ console.log("━━━ Step 1: Orchestrator repo ━━━");
187
+
188
+ for (const wfFile of CROSS_REPO_WORKFLOWS) {
189
+ const templatePath = path.join(
190
+ TEMPLATES_DIR,
191
+ "central",
192
+ ".github",
193
+ "workflows",
194
+ wfFile
195
+ );
196
+
197
+ if (!fs.existsSync(templatePath)) {
198
+ console.log(`⚠️ Template not found: ${wfFile} (skip)`);
199
+ continue;
200
+ }
201
+
202
+ const replacements = {
203
+ "{{GITHUB_OWNER}}": org,
204
+ "{{ORCHESTRATOR_REPO}}": orchestrator,
205
+ "{{MANAGED_REPOS}}": repos.map((r) => `${org}/${r}`).join(" "),
206
+ };
207
+
208
+ const content = readTemplate(templatePath, replacements);
209
+
210
+ if (dryRun) {
211
+ console.log(` [DRY] Would upload ${wfFile} to ${org}/${orchestrator}`);
212
+ } else {
213
+ const ok = await uploadWorkflow(
214
+ token,
215
+ org,
216
+ orchestrator,
217
+ wfFile,
218
+ content,
219
+ `chore: centralize ${wfFile} (solo-cto-agent setup --central)`
220
+ );
221
+ console.log(
222
+ ok
223
+ ? ` ✅ ${wfFile} → ${org}/${orchestrator}`
224
+ : ` ❌ Failed: ${wfFile}`
225
+ );
226
+ results.orchestrator.push({ file: wfFile, ok });
227
+ }
228
+ }
229
+
230
+ // Ensure they're enabled
231
+ const orchWorkflows = await getWorkflowsByName(
232
+ token,
233
+ org,
234
+ orchestrator,
235
+ CROSS_REPO_WORKFLOWS
236
+ );
237
+ for (const wf of orchWorkflows) {
238
+ if (wf.state !== "active") {
239
+ if (!dryRun) await enableWorkflow(token, org, orchestrator, wf.id);
240
+ console.log(` 🔄 Enabled: ${wf.name}`);
241
+ }
242
+ }
243
+
244
+ // ──────────────────────────────────────────────
245
+ // Step 2: Product repos — disable cross-repo workflows
246
+ // ──────────────────────────────────────────────
247
+ console.log("\n━━━ Step 2: Product repos — disable duplicates ━━━");
248
+
249
+ // Auto-detect repos if none specified
250
+ let productRepos = repos;
251
+ if (productRepos.length === 0) {
252
+ console.log(" Auto-detecting repos with cross-repo workflows...");
253
+ const allRepos = await githubRequest(
254
+ "GET",
255
+ `/users/${org}/repos?per_page=100&sort=updated`,
256
+ token
257
+ );
258
+ if (allRepos.status === 200) {
259
+ for (const r of allRepos.data) {
260
+ if (r.name === orchestrator || r.fork || r.archived) continue;
261
+ const crossWfs = await getWorkflowsByName(
262
+ token,
263
+ org,
264
+ r.name,
265
+ CROSS_REPO_WORKFLOWS
266
+ );
267
+ if (crossWfs.some((w) => w.state === "active")) {
268
+ productRepos.push(r.name);
269
+ }
270
+ }
271
+ }
272
+ if (productRepos.length > 0) {
273
+ console.log(
274
+ ` Found ${productRepos.length}: ${productRepos.join(", ")}`
275
+ );
276
+ }
277
+ }
278
+
279
+ for (const repo of productRepos) {
280
+ const crossWfs = await getWorkflowsByName(
281
+ token,
282
+ org,
283
+ repo,
284
+ CROSS_REPO_WORKFLOWS
285
+ );
286
+
287
+ for (const wf of crossWfs) {
288
+ if (wf.state === "active") {
289
+ if (dryRun) {
290
+ console.log(` [DRY] Would disable ${wf.name} in ${repo}`);
291
+ } else {
292
+ const ok = await disableWorkflow(token, org, repo, wf.id);
293
+ console.log(
294
+ ok
295
+ ? ` ✅ Disabled ${wf.name} in ${repo}`
296
+ : ` ❌ Failed to disable ${wf.name} in ${repo}`
297
+ );
298
+ results.disabled.push({ repo, workflow: wf.name, ok });
299
+ }
300
+ } else {
301
+ console.log(` ⏭️ ${wf.name} in ${repo} — already disabled`);
302
+ }
303
+ }
304
+ }
305
+
306
+ // ──────────────────────────────────────────────
307
+ // Step 3: Summary
308
+ // ──────────────────────────────────────────────
309
+ console.log("\n━━━ Summary ━━━");
310
+ console.log("");
311
+ console.log(" Architecture:");
312
+ console.log(` Central (${orchestrator}):`);
313
+ console.log(" ├─ telegram-bot-runner.yml (every 15min)");
314
+ console.log(" └─ telegram-digest.yml (every 30min)");
315
+ console.log(" Product repos:");
316
+ console.log(" ├─ solo-cto-review.yml (PR review)");
317
+ console.log(" ├─ solo-cto-pipeline.yml (orchestration)");
318
+ console.log(" └─ telegram-notify.yml (per-repo events)");
319
+ console.log("");
320
+ console.log(" Cross-repo workflows are centralized. No more duplicate notifications.");
321
+ console.log("");
322
+
323
+ // Secrets reminder
324
+ console.log(" ⚠️ Required secrets in orchestrator repo:");
325
+ console.log(" - TELEGRAM_BOT_TOKEN");
326
+ console.log(" - TELEGRAM_CHAT_ID");
327
+ console.log(" - ORCHESTRATOR_PAT (repo + workflow scopes)");
328
+ console.log("");
329
+
330
+ return results;
331
+ }
332
+
333
+ // ─── CLI Entry ─────────────────────────────────────
334
+
335
+ if (require.main === module) {
336
+ const args = process.argv.slice(2);
337
+ const getArg = (name) => {
338
+ const idx = args.indexOf(name);
339
+ return idx >= 0 && args[idx + 1] ? args[idx + 1] : null;
340
+ };
341
+
342
+ centralSetup({
343
+ org: getArg("--org") || process.env.GITHUB_ORG || "",
344
+ orchestrator:
345
+ getArg("--orchestrator") || "dual-agent-review-orchestrator",
346
+ repos: getArg("--repos") ? getArg("--repos").split(",") : [],
347
+ token: process.env.GITHUB_TOKEN || "",
348
+ dryRun: args.includes("--dry-run"),
349
+ });
350
+ }
351
+
352
+ module.exports = { centralSetup };