@gh-symphony/cli 0.0.17 → 0.0.19

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 (32) hide show
  1. package/README.md +105 -9
  2. package/dist/{chunk-EFMFGOWM.js → chunk-6CI3UUMH.js} +282 -57
  3. package/dist/chunk-C7G7RJ4G.js +146 -0
  4. package/dist/{chunk-MHIWAIVD.js → chunk-GKENCODJ.js} +141 -53
  5. package/dist/{project-557FE2GD.js → chunk-H2YXSYOZ.js} +108 -92
  6. package/dist/{chunk-TF3QNWNC.js → chunk-M3IFVLQS.js} +246 -212
  7. package/dist/{chunk-IWR4UQEJ.js → chunk-RN2PACNV.js} +350 -523
  8. package/dist/chunk-TILHWBP6.js +638 -0
  9. package/dist/{chunk-6HBZC3BE.js → chunk-XN5ABWZ6.js} +23 -5
  10. package/dist/{chunk-76QPITKI.js → chunk-Y6TYJMNT.js} +1 -1
  11. package/dist/{config-cmd-AZ7POMAA.js → config-cmd-DNXNL26Z.js} +3 -1
  12. package/dist/doctor-IYHCFXOZ.js +1126 -0
  13. package/dist/index.js +157 -19
  14. package/dist/init-KZT6YNOH.js +33 -0
  15. package/dist/{logs-6LNGT2GF.js → logs-6JKKYDGJ.js} +1 -1
  16. package/dist/project-DNALEWO3.js +22 -0
  17. package/dist/{recover-LVBI2TGH.js → recover-C3V2QAUB.js} +3 -3
  18. package/dist/repo-HDDE7OUI.js +321 -0
  19. package/dist/{run-WITYAYFZ.js → run-XI2S5Y4V.js} +3 -3
  20. package/dist/setup-K4CYYJBF.js +431 -0
  21. package/dist/{start-JUFKNL3N.js → start-M6IQGRFO.js} +5 -5
  22. package/dist/{status-3WK5BWRZ.js → status-QSCFVGRQ.js} +2 -2
  23. package/dist/{stop-AA3AP5M6.js → stop-7MFCBQVW.js} +2 -2
  24. package/dist/upgrade-F4VE4XBS.js +165 -0
  25. package/dist/{version-YVM2A25J.js → version-Y5RYNWMF.js} +1 -1
  26. package/dist/worker-entry.js +39 -11
  27. package/dist/workflow-TBIFY5MO.js +497 -0
  28. package/package.json +4 -4
  29. package/dist/chunk-JO3AXHQI.js +0 -130
  30. package/dist/chunk-TH5QPO3Y.js +0 -67
  31. package/dist/init-EZXQAXZM.js +0 -17
  32. package/dist/repo-R3XBIVAX.js +0 -121
package/dist/index.js CHANGED
@@ -15,7 +15,10 @@ import {
15
15
 
16
16
  // src/completion.ts
17
17
  var TOP_LEVEL_COMMANDS = [
18
- "init",
18
+ "workflow",
19
+ "setup",
20
+ "doctor",
21
+ "upgrade",
19
22
  "start",
20
23
  "stop",
21
24
  "status",
@@ -44,6 +47,35 @@ var GLOBAL_OPTIONS = [
44
47
  var GLOBAL_OPTIONS_WITH_VALUES = ["--config", "--config-dir"];
45
48
  var COMMAND_OPTIONS = {
46
49
  completion: ["bash", "zsh", "fish"],
50
+ workflow: ["init", "validate", "preview"],
51
+ "workflow:init": [
52
+ "--non-interactive",
53
+ "--project",
54
+ "--output",
55
+ "--skip-skills",
56
+ "--skip-context",
57
+ "--dry-run",
58
+ ...GLOBAL_OPTIONS
59
+ ],
60
+ "workflow:validate": ["--file", ...GLOBAL_OPTIONS],
61
+ "workflow:preview": [
62
+ "--file",
63
+ "--sample",
64
+ "--attempt",
65
+ ...GLOBAL_OPTIONS
66
+ ],
67
+ setup: [
68
+ "--non-interactive",
69
+ "--project",
70
+ "--workspace-dir",
71
+ "--assigned-only",
72
+ "--output",
73
+ "--skip-skills",
74
+ "--skip-context",
75
+ ...GLOBAL_OPTIONS
76
+ ],
77
+ doctor: ["--project-id", "--project", ...GLOBAL_OPTIONS],
78
+ upgrade: [...GLOBAL_OPTIONS],
47
79
  start: ["--project-id", "--project", "--daemon", "-d", ...GLOBAL_OPTIONS],
48
80
  stop: ["--project-id", "--project", "--force", ...GLOBAL_OPTIONS],
49
81
  status: ["--project-id", "--project", "--watch", "-w", ...GLOBAL_OPTIONS],
@@ -85,10 +117,11 @@ var COMMAND_OPTIONS = {
85
117
  "-w",
86
118
  ...GLOBAL_OPTIONS
87
119
  ],
88
- repo: ["list", "add", "remove"],
120
+ repo: ["list", "add", "remove", "sync"],
89
121
  "repo:list": [...GLOBAL_OPTIONS],
90
122
  "repo:add": [...GLOBAL_OPTIONS],
91
123
  "repo:remove": [...GLOBAL_OPTIONS],
124
+ "repo:sync": ["--dry-run", "--prune", ...GLOBAL_OPTIONS],
92
125
  config: ["show", "set", "edit"],
93
126
  "config:show": [...GLOBAL_OPTIONS],
94
127
  "config:set": [...GLOBAL_OPTIONS],
@@ -107,7 +140,7 @@ function renderBashCasePatterns() {
107
140
  return
108
141
  ;;`;
109
142
  }
110
- if (command === "project" || command === "repo" || command === "config") {
143
+ if (command === "workflow" || command === "project" || command === "repo" || command === "config") {
111
144
  return ` ${command})
112
145
  COMPREPLY=( $(compgen -W "${quoteWords(values)}" -- "$cur") )
113
146
  return
@@ -148,6 +181,11 @@ function renderFishLines() {
148
181
  `complete -c gh-symphony -f -n '__fish_seen_subcommand_from config' -a '${subcommand}'`
149
182
  );
150
183
  }
184
+ for (const subcommand of COMMAND_OPTIONS.workflow ?? []) {
185
+ lines.push(
186
+ `complete -c gh-symphony -f -n '__fish_seen_subcommand_from workflow' -a '${subcommand}'`
187
+ );
188
+ }
151
189
  for (const shell of COMMAND_OPTIONS.completion ?? []) {
152
190
  lines.push(
153
191
  `complete -c gh-symphony -f -n '__fish_seen_subcommand_from completion' -a '${shell}'`
@@ -217,7 +255,7 @@ _gh_symphony_completion() {
217
255
  return
218
256
  fi
219
257
 
220
- if [[ "\${path}" == "project" || "\${path}" == "repo" || "\${path}" == "config" || "\${path}" == "completion" ]]; then
258
+ if [[ "\${path}" == "workflow" || "\${path}" == "project" || "\${path}" == "repo" || "\${path}" == "config" || "\${path}" == "completion" ]]; then
221
259
  if [[ -n "\${GH_SYMPHONY_SUBCOMMAND}" ]]; then
222
260
  path="\${path}:\${GH_SYMPHONY_SUBCOMMAND}"
223
261
  fi
@@ -240,17 +278,21 @@ ${bashFunction}complete -F _gh_symphony_completion gh-symphony
240
278
 
241
279
  // src/index.ts
242
280
  var COMMANDS = {
243
- init: () => import("./init-EZXQAXZM.js"),
244
- start: () => import("./start-JUFKNL3N.js"),
245
- stop: () => import("./stop-AA3AP5M6.js"),
246
- status: () => import("./status-3WK5BWRZ.js"),
247
- run: () => import("./run-WITYAYFZ.js"),
248
- recover: () => import("./recover-LVBI2TGH.js"),
249
- logs: () => import("./logs-6LNGT2GF.js"),
250
- project: () => import("./project-557FE2GD.js"),
251
- repo: () => import("./repo-R3XBIVAX.js"),
252
- config: () => import("./config-cmd-AZ7POMAA.js"),
253
- version: () => import("./version-YVM2A25J.js")
281
+ workflow: () => import("./workflow-TBIFY5MO.js"),
282
+ init: () => import("./init-KZT6YNOH.js"),
283
+ setup: () => import("./setup-K4CYYJBF.js"),
284
+ doctor: () => import("./doctor-IYHCFXOZ.js"),
285
+ upgrade: () => import("./upgrade-F4VE4XBS.js"),
286
+ start: () => import("./start-M6IQGRFO.js"),
287
+ stop: () => import("./stop-7MFCBQVW.js"),
288
+ status: () => import("./status-QSCFVGRQ.js"),
289
+ run: () => import("./run-XI2S5Y4V.js"),
290
+ recover: () => import("./recover-C3V2QAUB.js"),
291
+ logs: () => import("./logs-6JKKYDGJ.js"),
292
+ project: () => import("./project-DNALEWO3.js"),
293
+ repo: () => import("./repo-HDDE7OUI.js"),
294
+ config: () => import("./config-cmd-DNXNL26Z.js"),
295
+ version: () => import("./version-Y5RYNWMF.js")
254
296
  };
255
297
  function addGlobalOptions(command) {
256
298
  return command.option("--config <dir>", "Config directory").addOption(new Option("--config-dir <dir>").hideHelp()).option("-v, --verbose", "Enable verbose output").option("--json", "Output in JSON format").option("--no-color", "Disable color output");
@@ -314,19 +356,104 @@ function createProgram() {
314
356
  new Command().name("gh-symphony").description("AI Coding Agent Orchestrator").exitOverride().helpOption("-h, --help", "Show help").addHelpCommand("help [command]", "Show help for command").showHelpAfterError("(run with --help for usage)").option("-V, --version", "Show version")
315
357
  );
316
358
  addGlobalOptions(
317
- program.command("init").description("Interactive project setup wizard").allowExcessArguments(false)
359
+ program.command("init", { hidden: true }).description("Alias for 'gh-symphony workflow init'").option("--non-interactive", "Run without prompts").option("--project <id>", "GitHub Project ID or URL").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").option("--dry-run", "Preview generated files without writing them").allowExcessArguments(false)
360
+ ).action(async function() {
361
+ markInvoked();
362
+ const values = this.optsWithGlobals();
363
+ const args = ["init"];
364
+ pushOption(args, "--non-interactive", values.nonInteractive);
365
+ pushOption(args, "--project", values.project);
366
+ pushOption(args, "--output", values.output);
367
+ pushOption(args, "--skip-skills", values.skipSkills);
368
+ pushOption(args, "--skip-context", values.skipContext);
369
+ pushOption(args, "--dry-run", values.dryRun);
370
+ await invokeHandler("workflow", args, values);
371
+ });
372
+ const workflow = addGlobalOptions(
373
+ program.command("workflow").description("Manage WORKFLOW.md authoring")
374
+ );
375
+ workflow.action(async function() {
376
+ markInvoked();
377
+ await invokeHandler(
378
+ "workflow",
379
+ [],
380
+ this.optsWithGlobals()
381
+ );
382
+ });
383
+ addGlobalOptions(
384
+ workflow.command("init").description("Generate WORKFLOW.md and workflow support files").option("--non-interactive", "Run without prompts").option("--project <id>", "GitHub Project ID or URL").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").option("--dry-run", "Preview generated files without writing them").allowExcessArguments(false)
318
385
  ).action(async function() {
319
386
  markInvoked();
320
- await invokeHandler("init", [], this.optsWithGlobals());
387
+ const values = this.optsWithGlobals();
388
+ const args = ["init"];
389
+ pushOption(args, "--non-interactive", values.nonInteractive);
390
+ pushOption(args, "--project", values.project);
391
+ pushOption(args, "--output", values.output);
392
+ pushOption(args, "--skip-skills", values.skipSkills);
393
+ pushOption(args, "--skip-context", values.skipContext);
394
+ pushOption(args, "--dry-run", values.dryRun);
395
+ await invokeHandler("workflow", args, values);
321
396
  });
322
397
  addGlobalOptions(
323
- program.command("start").description("Start the orchestrator").option("-d, --daemon", "Start in daemon mode").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
398
+ workflow.command("validate").description("Parse and strictly validate a WORKFLOW.md file").option("--file <path>", "Validate a custom WORKFLOW.md path").allowExcessArguments(false)
399
+ ).action(async function() {
400
+ markInvoked();
401
+ const values = this.optsWithGlobals();
402
+ const args = ["validate"];
403
+ pushOption(args, "--file", values.file);
404
+ await invokeHandler("workflow", args, values);
405
+ });
406
+ addGlobalOptions(
407
+ workflow.command("preview").description("Render the final worker prompt from a sample issue").option("--file <path>", "Read a custom WORKFLOW.md path").option("--sample <json>", "Read sample issue JSON from a file").option("--attempt <n>", "Render as retry attempt n").allowExcessArguments(false)
408
+ ).action(async function() {
409
+ markInvoked();
410
+ const values = this.optsWithGlobals();
411
+ const args = ["preview"];
412
+ pushOption(args, "--file", values.file);
413
+ pushOption(args, "--sample", values.sample);
414
+ pushOption(args, "--attempt", values.attempt);
415
+ await invokeHandler("workflow", args, values);
416
+ });
417
+ addGlobalOptions(
418
+ program.command("setup").description("Run the one-command first-run setup flow").option("--non-interactive", "Run without prompts").option("--project <id>", "GitHub Project ID or URL").option("--workspace-dir <path>", "Workspace directory").option("--assigned-only", "Limit processing to assigned issues").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").allowExcessArguments(false)
419
+ ).action(async function() {
420
+ markInvoked();
421
+ const values = this.optsWithGlobals();
422
+ const args = [];
423
+ pushOption(args, "--non-interactive", values.nonInteractive);
424
+ pushOption(args, "--project", values.project);
425
+ pushOption(args, "--workspace-dir", values.workspaceDir);
426
+ pushOption(args, "--assigned-only", values.assignedOnly);
427
+ pushOption(args, "--output", values.output);
428
+ pushOption(args, "--skip-skills", values.skipSkills);
429
+ pushOption(args, "--skip-context", values.skipContext);
430
+ await invokeHandler("setup", args, values);
431
+ });
432
+ addGlobalOptions(
433
+ program.command("doctor").description("Run diagnostics and optional first-run remediation").option("--project-id <projectId>", "Project identifier").option("--fix", "Apply safe remediation steps and print manual follow-ups").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
434
+ ).action(async function() {
435
+ markInvoked();
436
+ const values = this.optsWithGlobals();
437
+ const args = [];
438
+ pushOption(args, "--project-id", resolveProjectId(values));
439
+ pushOption(args, "--fix", values.fix);
440
+ await invokeHandler("doctor", args, values);
441
+ });
442
+ addGlobalOptions(
443
+ program.command("upgrade").description("Upgrade the CLI to the latest published version").allowExcessArguments(false)
444
+ ).action(async function() {
445
+ markInvoked();
446
+ await invokeHandler("upgrade", [], this.optsWithGlobals());
447
+ });
448
+ addGlobalOptions(
449
+ program.command("start").description("Start the orchestrator").option("-d, --daemon", "Start in daemon mode").option("--once", "Run a single orchestration tick and exit").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
324
450
  ).action(async function() {
325
451
  markInvoked();
326
452
  const values = this.optsWithGlobals();
327
453
  const args = [];
328
454
  pushOption(args, "--project-id", resolveProjectId(values));
329
455
  pushOption(args, "--daemon", values.daemon);
456
+ pushOption(args, "--once", values.once);
330
457
  pushOption(args, "--http", values.http);
331
458
  pushOption(args, "--log-level", values.logLevel);
332
459
  await invokeHandler("start", args, values);
@@ -422,13 +549,14 @@ function createProgram() {
422
549
  );
423
550
  });
424
551
  addGlobalOptions(
425
- project.command("start").description("Start a specific project").option("-d, --daemon", "Start in daemon mode").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
552
+ project.command("start").description("Start a specific project").option("-d, --daemon", "Start in daemon mode").option("--once", "Run a single orchestration tick and exit").option("--http [port]", "Expose dashboard and refresh endpoints over HTTP").option("--log-level <level>", "Orchestrator lifecycle log level").option("--project-id <projectId>", "Project identifier").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
426
553
  ).action(async function() {
427
554
  markInvoked();
428
555
  const values = this.optsWithGlobals();
429
556
  const args = ["start"];
430
557
  pushOption(args, "--project-id", resolveProjectId(values));
431
558
  pushOption(args, "--daemon", values.daemon);
559
+ pushOption(args, "--once", values.once);
432
560
  pushOption(args, "--http", values.http);
433
561
  pushOption(args, "--log-level", values.logLevel);
434
562
  await invokeHandler("project", args, values);
@@ -500,6 +628,16 @@ function createProgram() {
500
628
  this.optsWithGlobals()
501
629
  );
502
630
  });
631
+ addGlobalOptions(
632
+ repo.command("sync").description("Sync repositories from the active GitHub Project").option("--dry-run", "Preview repository changes without writing config").option("--prune", "Remove local repositories that are no longer linked").allowExcessArguments(false)
633
+ ).action(async function() {
634
+ markInvoked();
635
+ const values = this.optsWithGlobals();
636
+ const args = ["sync"];
637
+ pushOption(args, "--dry-run", values.dryRun);
638
+ pushOption(args, "--prune", values.prune);
639
+ await invokeHandler("repo", args, values);
640
+ });
503
641
  const config = addGlobalOptions(
504
642
  program.command("config").description("Manage CLI configuration")
505
643
  );
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ abortIfCancelled,
4
+ buildAutomaticStateMappings,
5
+ buildDryRunJsonResult,
6
+ generateProjectId,
7
+ init_default,
8
+ planEcosystem,
9
+ planWorkflowArtifacts,
10
+ promptStateMappings,
11
+ renderDryRunPreview,
12
+ resolveStatusField,
13
+ writeConfig,
14
+ writeEcosystem,
15
+ writeWorkflowPlan
16
+ } from "./chunk-RN2PACNV.js";
17
+ import "./chunk-TILHWBP6.js";
18
+ import "./chunk-ROGRTUFI.js";
19
+ export {
20
+ abortIfCancelled,
21
+ buildAutomaticStateMappings,
22
+ buildDryRunJsonResult,
23
+ init_default as default,
24
+ generateProjectId,
25
+ planEcosystem,
26
+ planWorkflowArtifacts,
27
+ promptStateMappings,
28
+ renderDryRunPreview,
29
+ resolveStatusField,
30
+ writeConfig,
31
+ writeEcosystem,
32
+ writeWorkflowPlan
33
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  handleMissingManagedProjectConfig,
4
4
  resolveManagedProjectConfig
5
- } from "./chunk-TH5QPO3Y.js";
5
+ } from "./chunk-C7G7RJ4G.js";
6
6
  import {
7
7
  orchestratorLogPath
8
8
  } from "./chunk-ROGRTUFI.js";
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ project_default,
4
+ promptProjectRegistrationOptions,
5
+ renderProjectRegistrationSummary
6
+ } from "./chunk-H2YXSYOZ.js";
7
+ import "./chunk-RN2PACNV.js";
8
+ import "./chunk-GKENCODJ.js";
9
+ import "./chunk-6CI3UUMH.js";
10
+ import "./chunk-M3IFVLQS.js";
11
+ import "./chunk-TILHWBP6.js";
12
+ import "./chunk-XN5ABWZ6.js";
13
+ import "./chunk-MVRF7BES.js";
14
+ import "./chunk-5NV3LSAJ.js";
15
+ import "./chunk-Y6TYJMNT.js";
16
+ import "./chunk-C7G7RJ4G.js";
17
+ import "./chunk-ROGRTUFI.js";
18
+ export {
19
+ project_default as default,
20
+ promptProjectRegistrationOptions,
21
+ renderProjectRegistrationSummary
22
+ };
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-EFMFGOWM.js";
5
- import "./chunk-TF3QNWNC.js";
4
+ } from "./chunk-6CI3UUMH.js";
5
+ import "./chunk-M3IFVLQS.js";
6
6
  import {
7
7
  resolveRuntimeRoot
8
8
  } from "./chunk-5NV3LSAJ.js";
9
9
  import {
10
10
  handleMissingManagedProjectConfig,
11
11
  resolveManagedProjectConfig
12
- } from "./chunk-TH5QPO3Y.js";
12
+ } from "./chunk-C7G7RJ4G.js";
13
13
  import "./chunk-ROGRTUFI.js";
14
14
 
15
15
  // src/commands/recover.ts
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GitHubScopeError,
4
+ checkRequiredScopes,
5
+ createClient,
6
+ getGhToken,
7
+ getProjectDetail,
8
+ validateToken
9
+ } from "./chunk-TILHWBP6.js";
10
+ import {
11
+ loadActiveProjectConfig,
12
+ loadGlobalConfig,
13
+ saveProjectConfig
14
+ } from "./chunk-ROGRTUFI.js";
15
+
16
+ // src/commands/repo.ts
17
+ var handler = async (args, options) => {
18
+ const [subcommand, ...rest] = args;
19
+ switch (subcommand) {
20
+ case "list":
21
+ await repoList(options);
22
+ break;
23
+ case "add":
24
+ await repoAdd(rest, options);
25
+ break;
26
+ case "remove":
27
+ await repoRemove(rest, options);
28
+ break;
29
+ case "sync":
30
+ await repoSync(rest, options);
31
+ break;
32
+ default:
33
+ process.stderr.write(
34
+ "Usage: gh-symphony repo <list|add|remove|sync> [repo]\n"
35
+ );
36
+ process.exitCode = 2;
37
+ }
38
+ };
39
+ var repo_default = handler;
40
+ function repoKey(repo) {
41
+ return `${repo.owner}/${repo.name}`.toLowerCase();
42
+ }
43
+ function toRepoConfigEntry(repo) {
44
+ return {
45
+ owner: repo.owner,
46
+ name: repo.name,
47
+ cloneUrl: repo.cloneUrl
48
+ };
49
+ }
50
+ function parseRepoSyncFlags(args) {
51
+ const flags = { dryRun: false, prune: false };
52
+ for (const arg of args) {
53
+ if (arg === "--dry-run") {
54
+ flags.dryRun = true;
55
+ } else if (arg === "--prune") {
56
+ flags.prune = true;
57
+ }
58
+ }
59
+ return flags;
60
+ }
61
+ function displayScopeError(error) {
62
+ const plural = error.requiredScopes.length === 1 ? "" : "s";
63
+ process.stderr.write(
64
+ `Token is missing required scope${plural}: ${error.requiredScopes.join(", ")}
65
+ `
66
+ );
67
+ const currentSet = new Set(error.currentScopes.map((scope) => scope.toLowerCase()));
68
+ const scopesToAdd = ["repo", "read:org", "project"].filter(
69
+ (scope) => !currentSet.has(scope)
70
+ );
71
+ const scopeArg = scopesToAdd.length > 0 ? scopesToAdd.join(",") : error.requiredScopes.join(",");
72
+ process.stderr.write(
73
+ `Run 'gh auth refresh --scopes ${scopeArg}' and try again.
74
+ `
75
+ );
76
+ }
77
+ function formatRepoSpec(repo) {
78
+ return `${repo.owner}/${repo.name}`;
79
+ }
80
+ function sortRepos(repos) {
81
+ return [...repos].sort(
82
+ (left, right) => formatRepoSpec(left).localeCompare(formatRepoSpec(right))
83
+ );
84
+ }
85
+ function renderRepoGroup(label, repos) {
86
+ if (repos.length === 0) {
87
+ return [`${label}: none`];
88
+ }
89
+ return [label, ...sortRepos(repos).map((repo) => ` ${formatRepoSpec(repo)}`)];
90
+ }
91
+ function buildSyncedRepositories(currentRepos, linkedMap, linkedRepositories, prune) {
92
+ const retained = currentRepos.filter((repo) => linkedMap.has(repoKey(repo)) || !prune).map((repo) => {
93
+ const linked = linkedMap.get(repoKey(repo));
94
+ return linked ? toRepoConfigEntry(linked) : { ...repo };
95
+ });
96
+ const currentKeys = new Set(currentRepos.map((repo) => repoKey(repo)));
97
+ const additions = sortRepos(
98
+ linkedRepositories.filter((repo) => !currentKeys.has(repoKey(repo))).map(toRepoConfigEntry)
99
+ );
100
+ return [...retained, ...additions];
101
+ }
102
+ function writeRepoSummary(summary, options) {
103
+ if (options.json) {
104
+ process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
105
+ return;
106
+ }
107
+ process.stdout.write(
108
+ [
109
+ `Repository sync ${summary.dryRun ? "preview" : "complete"} for ${summary.projectId}`,
110
+ `Mode: ${summary.prune ? "prune" : "additive"}`,
111
+ ...renderRepoGroup("Added", summary.added),
112
+ ...renderRepoGroup("Removed", summary.removed),
113
+ ...renderRepoGroup("Unchanged", summary.unchanged),
114
+ summary.dryRun ? "No config changes written." : "Configuration updated."
115
+ ].join("\n") + "\n"
116
+ );
117
+ }
118
+ async function repoList(options) {
119
+ const ws = await loadActiveProjectConfig(options.configDir);
120
+ if (!ws) {
121
+ process.stderr.write("No project configured.\n");
122
+ process.exitCode = 1;
123
+ return;
124
+ }
125
+ if (options.json) {
126
+ process.stdout.write(JSON.stringify(ws.repositories, null, 2) + "\n");
127
+ return;
128
+ }
129
+ process.stdout.write("Repositories:\n");
130
+ for (const repo of ws.repositories) {
131
+ process.stdout.write(` ${repo.owner}/${repo.name}
132
+ `);
133
+ }
134
+ }
135
+ async function repoAdd(args, options) {
136
+ const [repoSpec] = args;
137
+ if (!repoSpec || !repoSpec.includes("/")) {
138
+ process.stderr.write("Usage: gh-symphony repo add <owner/name>\n");
139
+ process.exitCode = 2;
140
+ return;
141
+ }
142
+ const global = await loadGlobalConfig(options.configDir);
143
+ if (!global?.activeProject) {
144
+ process.stderr.write("No active project.\n");
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ const ws = await loadActiveProjectConfig(options.configDir);
149
+ if (!ws) {
150
+ process.stderr.write("Project config missing.\n");
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ const [owner, name] = repoSpec.split("/");
155
+ if (!owner || !name) {
156
+ process.stderr.write("Invalid repo format. Use: owner/name\n");
157
+ process.exitCode = 2;
158
+ return;
159
+ }
160
+ if (ws.repositories.some(
161
+ (r) => r.owner === owner && r.name === name
162
+ )) {
163
+ process.stdout.write(`Repository ${repoSpec} is already configured.
164
+ `);
165
+ return;
166
+ }
167
+ ws.repositories.push({
168
+ owner,
169
+ name,
170
+ cloneUrl: `https://github.com/${owner}/${name}.git`
171
+ });
172
+ await saveProjectConfig(options.configDir, global.activeProject, ws);
173
+ process.stdout.write(`Added repository: ${repoSpec}
174
+ `);
175
+ }
176
+ async function repoRemove(args, options) {
177
+ const [repoSpec] = args;
178
+ if (!repoSpec || !repoSpec.includes("/")) {
179
+ process.stderr.write("Usage: gh-symphony repo remove <owner/name>\n");
180
+ process.exitCode = 2;
181
+ return;
182
+ }
183
+ const global = await loadGlobalConfig(options.configDir);
184
+ if (!global?.activeProject) {
185
+ process.stderr.write("No active project.\n");
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ const ws = await loadActiveProjectConfig(options.configDir);
190
+ if (!ws) {
191
+ process.stderr.write("Project config missing.\n");
192
+ process.exitCode = 1;
193
+ return;
194
+ }
195
+ const [owner, name] = repoSpec.split("/");
196
+ const idx = ws.repositories.findIndex(
197
+ (r) => r.owner === owner && r.name === name
198
+ );
199
+ if (idx === -1) {
200
+ process.stderr.write(`Repository ${repoSpec} is not configured.
201
+ `);
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+ ws.repositories.splice(idx, 1);
206
+ await saveProjectConfig(options.configDir, global.activeProject, ws);
207
+ process.stdout.write(`Removed repository: ${repoSpec}
208
+ `);
209
+ }
210
+ async function repoSync(args, options) {
211
+ const flags = parseRepoSyncFlags(args);
212
+ const global = await loadGlobalConfig(options.configDir);
213
+ if (!global?.activeProject) {
214
+ process.stderr.write("No active project.\n");
215
+ process.exitCode = 1;
216
+ return;
217
+ }
218
+ const ws = await loadActiveProjectConfig(options.configDir);
219
+ if (!ws) {
220
+ process.stderr.write("Project config missing.\n");
221
+ process.exitCode = 1;
222
+ return;
223
+ }
224
+ const projectBindingId = typeof ws.tracker.settings?.projectId === "string" ? ws.tracker.settings.projectId : ws.tracker.bindingId;
225
+ if (!projectBindingId) {
226
+ process.stderr.write(
227
+ "Active project is missing its GitHub Project binding. Re-run 'gh-symphony project add'.\n"
228
+ );
229
+ process.exitCode = 1;
230
+ return;
231
+ }
232
+ let token;
233
+ try {
234
+ token = getGhToken();
235
+ } catch {
236
+ process.stderr.write(
237
+ "Error: GitHub token not found. Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN.\n"
238
+ );
239
+ process.exitCode = 1;
240
+ return;
241
+ }
242
+ const client = createClient(token);
243
+ try {
244
+ const viewer = await validateToken(client);
245
+ const scopeCheck = checkRequiredScopes(viewer.scopes);
246
+ if (!scopeCheck.valid) {
247
+ process.stderr.write(
248
+ `Error: Missing required PAT scopes: ${scopeCheck.missing.join(", ")}
249
+ `
250
+ );
251
+ process.exitCode = 1;
252
+ return;
253
+ }
254
+ } catch {
255
+ process.stderr.write("Error: Invalid GitHub token.\n");
256
+ process.exitCode = 1;
257
+ return;
258
+ }
259
+ let projectDetail;
260
+ try {
261
+ projectDetail = await getProjectDetail(client, projectBindingId);
262
+ } catch (error) {
263
+ if (error instanceof GitHubScopeError) {
264
+ displayScopeError(error);
265
+ } else {
266
+ process.stderr.write(
267
+ `${error instanceof Error ? error.message : "Failed to load linked repositories."}
268
+ `
269
+ );
270
+ }
271
+ process.exitCode = 1;
272
+ return;
273
+ }
274
+ const currentRepos = ws.repositories;
275
+ const currentMap = new Map(
276
+ currentRepos.map((repo) => [repoKey(repo), repo])
277
+ );
278
+ const linkedMap = new Map(
279
+ projectDetail.linkedRepositories.map((repo) => [
280
+ repoKey(repo),
281
+ repo
282
+ ])
283
+ );
284
+ const added = projectDetail.linkedRepositories.filter((repo) => !currentMap.has(repoKey(repo))).map(toRepoConfigEntry);
285
+ const removed = flags.prune ? currentRepos.filter((repo) => !linkedMap.has(repoKey(repo))).map((repo) => ({ ...repo })) : [];
286
+ const unchanged = flags.prune ? currentRepos.filter((repo) => linkedMap.has(repoKey(repo))).map((repo) => {
287
+ const linked = linkedMap.get(repoKey(repo));
288
+ return linked ? toRepoConfigEntry(linked) : { ...repo };
289
+ }) : currentRepos.map((repo) => {
290
+ const linked = linkedMap.get(repoKey(repo));
291
+ return linked ? toRepoConfigEntry(linked) : { ...repo };
292
+ });
293
+ const nextRepositories = buildSyncedRepositories(
294
+ currentRepos,
295
+ linkedMap,
296
+ projectDetail.linkedRepositories,
297
+ flags.prune
298
+ );
299
+ if (!flags.dryRun) {
300
+ await saveProjectConfig(options.configDir, global.activeProject, {
301
+ ...ws,
302
+ repositories: nextRepositories
303
+ });
304
+ }
305
+ writeRepoSummary(
306
+ {
307
+ projectId: global.activeProject,
308
+ githubProjectId: projectBindingId,
309
+ dryRun: flags.dryRun,
310
+ prune: flags.prune,
311
+ added,
312
+ removed,
313
+ unchanged,
314
+ repositories: nextRepositories
315
+ },
316
+ options
317
+ );
318
+ }
319
+ export {
320
+ repo_default as default
321
+ };