@gh-symphony/cli 0.0.20 → 0.0.22

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 (40) hide show
  1. package/README.md +66 -2
  2. package/dist/chunk-2TSM3INR.js +1085 -0
  3. package/dist/chunk-2UW7NQLX.js +684 -0
  4. package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
  5. package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
  6. package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
  7. package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
  8. package/dist/{chunk-EKKT5USP.js → chunk-E7HYEEZD.js} +487 -133
  9. package/dist/chunk-EEQQWTXS.js +3257 -0
  10. package/dist/chunk-GDE6FYN4.js +26 -0
  11. package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
  12. package/dist/{chunk-RN2PACNV.js → chunk-HMLBBZNY.js} +731 -75
  13. package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
  14. package/dist/{chunk-HZVDTAPS.js → chunk-PUDXVBSN.js} +1549 -1458
  15. package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
  16. package/dist/{chunk-3AWF54PI.js → chunk-ZHOKYUO3.js} +394 -42
  17. package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
  18. package/dist/{doctor-IYHCFXOZ.js → doctor-EJUMPBMW.js} +105 -40
  19. package/dist/index.js +112 -24
  20. package/dist/{init-KZT6YNOH.js → init-54HMKNYI.js} +8 -3
  21. package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
  22. package/dist/project-RMYMZSFV.js +25 -0
  23. package/dist/{recover-5KQI7WH5.js → recover-LTLKMTRX.js} +7 -5
  24. package/dist/repo-WI7GF6XQ.js +749 -0
  25. package/dist/{run-ETC5UTRA.js → run-IHN3ZL35.js} +21 -7
  26. package/dist/{setup-VWB7RZUQ.js → setup-TZJSM3QV.js} +53 -14
  27. package/dist/start-RTAHQMR2.js +19 -0
  28. package/dist/status-F4D52OVK.js +12 -0
  29. package/dist/stop-MDKMJPVR.js +10 -0
  30. package/dist/{upgrade-3YNF3VKY.js → upgrade-O33S2SJK.js} +2 -2
  31. package/dist/{version-NUBTTOG7.js → version-CW54Q7BK.js} +1 -1
  32. package/dist/worker-entry.js +848 -693
  33. package/dist/{workflow-TBIFY5MO.js → workflow-L3KT6HB7.js} +177 -11
  34. package/package.json +4 -2
  35. package/dist/chunk-M3IFVLQS.js +0 -1155
  36. package/dist/project-UUVHS3ZR.js +0 -22
  37. package/dist/repo-HDDE7OUI.js +0 -321
  38. package/dist/start-ENFLZUI6.js +0 -16
  39. package/dist/status-QSCFVGRQ.js +0 -11
  40. package/dist/stop-7MFCBQVW.js +0 -9
@@ -54,9 +54,15 @@ async function saveGlobalConfig(configDir, config) {
54
54
  await writeJsonFile(configFilePath(configDir), config);
55
55
  }
56
56
  async function loadProjectConfig(configDir, projectId) {
57
- return readJsonFile(
58
- projectConfigPath(configDir, projectId)
59
- );
57
+ const config = await readJsonFile(projectConfigPath(configDir, projectId));
58
+ if (!config) {
59
+ return null;
60
+ }
61
+ const repository = config.repository ?? firstConfiguredRepository(config);
62
+ return {
63
+ ...config,
64
+ ...repository ? { repository } : {}
65
+ };
60
66
  }
61
67
  async function saveProjectConfig(configDir, projectId, config) {
62
68
  await writeJsonFile(projectConfigPath(configDir, projectId), config);
@@ -91,6 +97,11 @@ function isFileMissing(error) {
91
97
  error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
92
98
  );
93
99
  }
100
+ function firstConfiguredRepository(config) {
101
+ return config.repositories?.find(
102
+ (repository) => typeof repository.owner === "string" && repository.owner.length > 0 && typeof repository.name === "string" && repository.name.length > 0
103
+ );
104
+ }
94
105
 
95
106
  export {
96
107
  resolveConfigDir,
@@ -2,34 +2,57 @@
2
2
  import {
3
3
  abortIfCancelled,
4
4
  generateProjectId,
5
+ warnIfProjectDiscoveryPartial,
5
6
  writeConfig
6
- } from "./chunk-RN2PACNV.js";
7
+ } from "./chunk-HMLBBZNY.js";
7
8
  import {
8
9
  start_default
9
- } from "./chunk-EKKT5USP.js";
10
+ } from "./chunk-E7HYEEZD.js";
11
+ import {
12
+ explainIssueDispatch,
13
+ isActiveRunRecordStatus,
14
+ parseIssueIdentifier,
15
+ resolveTrackerAdapter
16
+ } from "./chunk-PUDXVBSN.js";
17
+ import {
18
+ findGithubProjectIssue
19
+ } from "./chunk-2TSM3INR.js";
10
20
  import {
11
21
  GhAuthError,
12
22
  GitHubScopeError,
13
23
  checkRequiredScopes,
14
24
  createClient,
25
+ discoverUserProjects,
26
+ getGhToken,
15
27
  getGhTokenWithSource,
16
28
  getProjectDetail,
17
29
  listUserProjects,
18
30
  resolveGitHubAuth,
19
31
  validateToken
20
- } from "./chunk-TILHWBP6.js";
32
+ } from "./chunk-C67H3OUL.js";
33
+ import {
34
+ WorkflowConfigStore
35
+ } from "./chunk-EEQQWTXS.js";
21
36
  import {
22
37
  status_default
23
- } from "./chunk-XN5ABWZ6.js";
38
+ } from "./chunk-DFLXHNYQ.js";
24
39
  import {
25
- stripAnsi
26
- } from "./chunk-MVRF7BES.js";
40
+ bold,
41
+ green,
42
+ red,
43
+ stripAnsi,
44
+ yellow
45
+ } from "./chunk-36KYEDEO.js";
27
46
  import {
28
47
  resolveRuntimeRoot
29
- } from "./chunk-5NV3LSAJ.js";
48
+ } from "./chunk-IWFX2FMA.js";
30
49
  import {
31
50
  stop_default
32
- } from "./chunk-Y6TYJMNT.js";
51
+ } from "./chunk-GSX2FV3M.js";
52
+ import {
53
+ handleMissingManagedProjectConfig,
54
+ resolveManagedProjectConfig
55
+ } from "./chunk-DDL4BWSL.js";
33
56
  import {
34
57
  daemonPidPath,
35
58
  httpStatusPath,
@@ -37,16 +60,42 @@ import {
37
60
  loadProjectConfig,
38
61
  projectConfigDir,
39
62
  saveGlobalConfig
40
- } from "./chunk-ROGRTUFI.js";
63
+ } from "./chunk-QIRE2VXS.js";
41
64
 
42
65
  // src/commands/project.ts
43
66
  import * as p from "@clack/prompts";
44
67
  import { execFile as execFileCallback } from "child_process";
45
68
  import { promisify } from "util";
46
- import { readFile } from "fs/promises";
69
+ import { readdir, readFile } from "fs/promises";
47
70
  import { join } from "path";
71
+ import { resolve } from "path";
48
72
  var execFile = promisify(execFileCallback);
49
73
  var KNOWN_REQUIRED_SCOPES = ["repo", "read:org", "project"];
74
+ function formatProjectRepoSummary(selectedRepos, totalLinked) {
75
+ if (totalLinked === 0) {
76
+ return "none linked yet (0 linked)";
77
+ }
78
+ if (selectedRepos.length === totalLinked) {
79
+ return `${selectedRepos.map((repo) => `${repo.owner}/${repo.name}`).join(", ")} (all ${selectedRepos.length} linked)`;
80
+ }
81
+ if (selectedRepos.length === 0) {
82
+ return `none selected (0 of ${totalLinked} linked)`;
83
+ }
84
+ return `${selectedRepos.map((repo) => `${repo.owner}/${repo.name}`).join(", ")} (${selectedRepos.length} of ${totalLinked} linked)`;
85
+ }
86
+ function projectCreatedMessage(projectId, repositoryCount) {
87
+ const lines = [
88
+ `Project "${projectId}" created with ${repositoryCount} repositor${repositoryCount === 1 ? "y" : "ies"}.`,
89
+ "Run 'gh-symphony start' to begin orchestration."
90
+ ];
91
+ if (repositoryCount === 0) {
92
+ lines.push(
93
+ "Next step: run 'gh-symphony repo add <owner/name>' to register a repository.",
94
+ "Or add a repo-linked issue to the GitHub Project and re-run setup later."
95
+ );
96
+ }
97
+ return lines.join("\n");
98
+ }
50
99
  function displayScopeError(error, retryCommand) {
51
100
  const plural = error.requiredScopes.length === 1 ? "" : "s";
52
101
  p.log.error(
@@ -86,6 +135,47 @@ function parseProjectAddFlags(args) {
86
135
  }
87
136
  return flags;
88
137
  }
138
+ function parseProjectExplainFlags(args) {
139
+ const parsed = {};
140
+ for (let i = 0; i < args.length; i += 1) {
141
+ const arg = args[i];
142
+ if (arg === "--project" || arg === "--project-id") {
143
+ const value = args[i + 1];
144
+ if (!value || value.startsWith("-")) {
145
+ parsed.error = `Option '${arg}' argument missing`;
146
+ return parsed;
147
+ }
148
+ parsed.projectId = value;
149
+ i += 1;
150
+ continue;
151
+ }
152
+ if (arg === "--workflow" || arg === "--workflow-path") {
153
+ const value = args[i + 1];
154
+ if (!value || value.startsWith("-")) {
155
+ parsed.error = `Option '${arg}' argument missing`;
156
+ return parsed;
157
+ }
158
+ parsed.workflowPath = value;
159
+ i += 1;
160
+ continue;
161
+ }
162
+ if (arg?.startsWith("-")) {
163
+ parsed.error = `Unknown option '${arg}'`;
164
+ return parsed;
165
+ }
166
+ if (parsed.identifier) {
167
+ parsed.error = "Only one issue identifier can be explained at a time";
168
+ return parsed;
169
+ }
170
+ parsed.identifier = arg;
171
+ }
172
+ if (!parsed.identifier) {
173
+ parsed.error = "Issue identifier argument missing";
174
+ } else if (!parseIssueIdentifier(parsed.identifier)) {
175
+ parsed.error = "Issue identifier must use the form <owner>/<repo>#<number>";
176
+ }
177
+ return parsed;
178
+ }
89
179
  var handler = async (args, options) => {
90
180
  const [subcommand, ...rest] = args;
91
181
  switch (subcommand) {
@@ -110,9 +200,12 @@ var handler = async (args, options) => {
110
200
  case "status":
111
201
  await status_default(rest, options);
112
202
  return;
203
+ case "explain":
204
+ await projectExplain(rest, options);
205
+ return;
113
206
  default:
114
207
  process.stdout.write(
115
- "Usage: gh-symphony project <add|list|remove|start|stop|switch|status>\n"
208
+ "Usage: gh-symphony project <add|list|remove|start|stop|switch|status|explain>\n"
116
209
  );
117
210
  }
118
211
  };
@@ -210,9 +303,260 @@ async function readPersistedSnapshot(configDir, projectId) {
210
303
  async function fetchProjectSnapshot(configDir, projectId) {
211
304
  return readPersistedSnapshot(configDir, projectId);
212
305
  }
306
+ async function projectExplain(args, options) {
307
+ const parsed = parseProjectExplainFlags(args);
308
+ if (parsed.error) {
309
+ process.stderr.write(`${parsed.error}
310
+ `);
311
+ process.stderr.write(
312
+ "Usage: gh-symphony project explain <owner/repo#number> [--project-id <project-id>] [--workflow <path>]\n"
313
+ );
314
+ process.exitCode = 2;
315
+ return;
316
+ }
317
+ const projectConfig = await resolveManagedProjectConfig({
318
+ configDir: options.configDir,
319
+ requestedProjectId: parsed.projectId
320
+ });
321
+ if (!projectConfig) {
322
+ handleMissingManagedProjectConfig();
323
+ return;
324
+ }
325
+ const identifier = parsed.identifier;
326
+ const parsedIdentifier = parseIssueIdentifier(identifier);
327
+ const fallbackRepository = {
328
+ owner: parsedIdentifier.owner,
329
+ name: parsedIdentifier.name,
330
+ cloneUrl: `https://github.com/${parsedIdentifier.owner}/${parsedIdentifier.name}.git`
331
+ };
332
+ const workflowRepository = projectConfig.repository ?? fallbackRepository;
333
+ let token;
334
+ try {
335
+ token = getGhToken();
336
+ } catch (error) {
337
+ if (error instanceof GhAuthError) {
338
+ process.stderr.write(
339
+ `Error: GitHub authentication is required for project explain. ${error.message}
340
+ `
341
+ );
342
+ process.stderr.write(
343
+ "Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN, then re-run this command.\n"
344
+ );
345
+ process.exitCode = 2;
346
+ return;
347
+ }
348
+ throw error;
349
+ }
350
+ const trackerAdapter = resolveTrackerAdapter(projectConfig.tracker);
351
+ const orchestratorProject = {
352
+ ...projectConfig,
353
+ repository: workflowRepository
354
+ };
355
+ const trackerDependencies = {
356
+ token,
357
+ projectItemsCache: createProjectItemsCache()
358
+ };
359
+ const runtimeRoot = join(
360
+ resolveRuntimeRoot(options.configDir),
361
+ "projects",
362
+ projectConfig.projectId
363
+ );
364
+ const issuesPromise = trackerAdapter.listIssues(
365
+ orchestratorProject,
366
+ trackerDependencies
367
+ );
368
+ const issuePromise = projectConfig.tracker.adapter === "github-project" ? findGithubProjectIssue(
369
+ orchestratorProject,
370
+ identifier,
371
+ trackerDependencies
372
+ ) : issuesPromise.then(
373
+ (issues2) => issues2.find(
374
+ (candidate) => candidate.identifier.trim().toLowerCase() === identifier.trim().toLowerCase()
375
+ ) ?? null
376
+ );
377
+ const [issues, issue, issueRecords, runs, snapshot] = await Promise.all([
378
+ issuesPromise,
379
+ issuePromise,
380
+ readJsonFile(join(runtimeRoot, "issues.json")),
381
+ readRuns(runtimeRoot, projectConfig.projectId),
382
+ readPersistedSnapshot(options.configDir, projectConfig.projectId)
383
+ ]);
384
+ let workflow;
385
+ try {
386
+ workflow = await loadExplainWorkflow({
387
+ explicitWorkflowPath: parsed.workflowPath,
388
+ repository: workflowRepository,
389
+ runs
390
+ });
391
+ } catch (error) {
392
+ if (error instanceof ProjectExplainWorkflowError) {
393
+ process.stderr.write(`Error: ${error.message}
394
+ `);
395
+ process.stderr.write(
396
+ "Hint: pass --workflow <path-to-WORKFLOW.md> or run 'gh-symphony workflow preview --file <path>' to verify the workflow file.\n"
397
+ );
398
+ process.exitCode = 2;
399
+ return;
400
+ }
401
+ throw error;
402
+ }
403
+ const activeRunCount = runs.filter(
404
+ (run) => isActiveRunRecordStatus(run.status)
405
+ ).length;
406
+ const report = explainIssueDispatch({
407
+ identifier,
408
+ issue,
409
+ projectRepository: projectConfig.repository ?? null,
410
+ allIssues: issues,
411
+ lifecycle: workflow.lifecycle,
412
+ issueRecords: issueRecords ?? [],
413
+ runs,
414
+ activeRunCount,
415
+ maxConcurrentAgents: workflow.maxConcurrentAgents,
416
+ maxConcurrentAgentsByState: workflow.maxConcurrentAgentsByState
417
+ });
418
+ const enrichedReport = {
419
+ ...report,
420
+ project: {
421
+ id: projectConfig.projectId,
422
+ slug: projectConfig.slug,
423
+ tracker: projectConfig.tracker,
424
+ lastTickAt: snapshot?.lastTickAt ?? null,
425
+ health: snapshot?.health ?? null
426
+ }
427
+ };
428
+ if (options.json) {
429
+ process.stdout.write(JSON.stringify(enrichedReport, null, 2) + "\n");
430
+ return;
431
+ }
432
+ process.stdout.write(renderProjectExplainReport(report, options.noColor));
433
+ }
434
+ var ProjectExplainWorkflowError = class extends Error {
435
+ constructor(message) {
436
+ super(message);
437
+ this.name = "ProjectExplainWorkflowError";
438
+ }
439
+ };
440
+ async function loadExplainWorkflow(input) {
441
+ const workflowPaths = resolveExplainWorkflowCandidates(input);
442
+ if (workflowPaths.length === 0) {
443
+ throw new ProjectExplainWorkflowError(
444
+ "No WORKFLOW.md path could be resolved from --workflow, the configured repository path, or previous run records."
445
+ );
446
+ }
447
+ const failures = [];
448
+ for (const workflowPath of workflowPaths) {
449
+ try {
450
+ const resolution = await new WorkflowConfigStore().load(workflowPath);
451
+ return {
452
+ lifecycle: resolution.lifecycle,
453
+ maxConcurrentAgents: resolution.workflow.agent.maxConcurrentAgents,
454
+ maxConcurrentAgentsByState: resolution.workflow.agent.maxConcurrentAgentsByState
455
+ };
456
+ } catch (error) {
457
+ const message = error instanceof Error ? error.message : String(error);
458
+ failures.push(`${workflowPath}: ${message}`);
459
+ }
460
+ }
461
+ throw new ProjectExplainWorkflowError(
462
+ `Unable to load WORKFLOW.md for project explain. Checked: ${failures.join("; ")}`
463
+ );
464
+ }
465
+ function resolveExplainWorkflowCandidates(input) {
466
+ const paths = [];
467
+ if (input.explicitWorkflowPath) {
468
+ paths.push(resolve(input.explicitWorkflowPath));
469
+ }
470
+ if (input.repository.path) {
471
+ paths.push(join(resolve(input.repository.path), "WORKFLOW.md"));
472
+ }
473
+ const newestRuns = [...input.runs].sort(
474
+ (left, right) => (Date.parse(right.updatedAt) || 0) - (Date.parse(left.updatedAt) || 0)
475
+ );
476
+ for (const run of newestRuns) {
477
+ if (run.workflowPath) {
478
+ paths.push(resolve(run.workflowPath));
479
+ }
480
+ if (run.workingDirectory) {
481
+ paths.push(join(resolve(run.workingDirectory), "WORKFLOW.md"));
482
+ }
483
+ }
484
+ return [...new Set(paths)];
485
+ }
486
+ function createProjectItemsCache() {
487
+ const entries = /* @__PURE__ */ new Map();
488
+ return {
489
+ getOrLoad(key, load) {
490
+ const cached = entries.get(key);
491
+ if (cached) {
492
+ return cached;
493
+ }
494
+ const pending = load().catch((error) => {
495
+ entries.delete(key);
496
+ throw error;
497
+ });
498
+ entries.set(key, pending);
499
+ return pending;
500
+ }
501
+ };
502
+ }
503
+ async function readRuns(runtimeRoot, projectId) {
504
+ let runIds;
505
+ try {
506
+ runIds = await readdir(join(runtimeRoot, "runs"));
507
+ } catch {
508
+ return [];
509
+ }
510
+ const runs = await Promise.all(
511
+ runIds.map(
512
+ (runId) => readJsonFile(
513
+ join(runtimeRoot, "runs", runId, "run.json")
514
+ )
515
+ )
516
+ );
517
+ return runs.filter(
518
+ (run) => run !== null && run.projectId === projectId
519
+ );
520
+ }
521
+ async function readJsonFile(path) {
522
+ try {
523
+ return JSON.parse(await readFile(path, "utf8"));
524
+ } catch {
525
+ return null;
526
+ }
527
+ }
528
+ function renderProjectExplainReport(report, noColor) {
529
+ const apply = noColor ? (value) => stripAnsi(value) : (value) => value;
530
+ const lines = [
531
+ apply(bold(`Issue dispatch explanation: ${report.issue.identifier}`)),
532
+ report.summary,
533
+ "",
534
+ `State: ${report.issue.state ?? "unknown"}`,
535
+ `Repository: ${report.issue.repository}`,
536
+ "",
537
+ "Checks:"
538
+ ];
539
+ for (const check of report.checks) {
540
+ const marker = check.status === "pass" ? green("\u2713") : check.status === "warn" ? yellow("!") : red("\u2717");
541
+ lines.push(` ${apply(marker)} ${check.message}`);
542
+ if (check.hint) {
543
+ lines.push(` Hint: ${check.hint}`);
544
+ }
545
+ }
546
+ lines.push("");
547
+ lines.push("Related commands:");
548
+ lines.push(" gh-symphony workflow preview");
549
+ lines.push(" gh-symphony doctor");
550
+ lines.push(" gh-symphony project status");
551
+ lines.push(" gh-symphony logs --issue " + report.issue.identifier);
552
+ return lines.join("\n") + "\n";
553
+ }
213
554
  async function readHttpEndpoint(configDir, projectId) {
214
555
  try {
215
- const content = await readFile(httpStatusPath(configDir, projectId), "utf8");
556
+ const content = await readFile(
557
+ httpStatusPath(configDir, projectId),
558
+ "utf8"
559
+ );
216
560
  const state = JSON.parse(content);
217
561
  return typeof state.endpoint === "string" && state.endpoint.length > 0 ? state.endpoint : null;
218
562
  } catch {
@@ -224,7 +568,12 @@ async function readProcessUptime(pid) {
224
568
  return "-";
225
569
  }
226
570
  try {
227
- const { stdout } = await execFile("ps", ["-o", "etime=", "-p", String(pid)]);
571
+ const { stdout } = await execFile("ps", [
572
+ "-o",
573
+ "etime=",
574
+ "-p",
575
+ String(pid)
576
+ ]);
228
577
  const seconds = parsePsElapsedTime(stdout);
229
578
  return seconds === null ? "-" : formatDuration(seconds);
230
579
  } catch {
@@ -370,10 +719,9 @@ async function projectAddNonInteractive(flags, options) {
370
719
  JSON.stringify({ projectId, status: "created" }) + "\n"
371
720
  );
372
721
  } else {
373
- process.stdout.write(`Project created: ${projectId}
374
- `);
375
- process.stdout.write(`Run 'gh-symphony start' to begin orchestration.
376
- `);
722
+ process.stdout.write(
723
+ projectCreatedMessage(projectId, project.linkedRepositories.length) + "\n"
724
+ );
377
725
  }
378
726
  }
379
727
  async function projectAddInteractive(flags, options) {
@@ -417,10 +765,12 @@ async function projectAddInteractive(flags, options) {
417
765
  s2.start("Loading GitHub Project boards...");
418
766
  let projects;
419
767
  try {
420
- projects = await listUserProjects(client);
768
+ const discovery = await discoverUserProjects(client);
769
+ projects = discovery.projects;
421
770
  s2.stop(
422
771
  `Found ${projects.length} project${projects.length === 1 ? "" : "s"}`
423
772
  );
773
+ warnIfProjectDiscoveryPartial(discovery);
424
774
  } catch (error) {
425
775
  s2.stop("Failed to load projects.");
426
776
  if (error instanceof GitHubScopeError) {
@@ -463,10 +813,8 @@ async function projectAddInteractive(flags, options) {
463
813
  }
464
814
  if (projectDetail.linkedRepositories.length === 0) {
465
815
  p.log.warn(
466
- "No linked repositories found in this project. Add issues from repositories to the project first."
816
+ "No linked repositories found in this project. Add issues from repositories to the project, or run 'gh-symphony repo add owner/name' to validate and save a repository before your first orchestration run."
467
817
  );
468
- process.exitCode = 1;
469
- return;
470
818
  }
471
819
  const {
472
820
  assignedOnly: promptAssignedOnly,
@@ -479,7 +827,10 @@ async function projectAddInteractive(flags, options) {
479
827
  assignedOnlyInitialValue: flags.assignedOnly
480
828
  });
481
829
  const assignedOnly = flags.assignedOnly || promptAssignedOnly;
482
- const repoSummary = selectedRepos.length === projectDetail.linkedRepositories.length ? `${selectedRepos.map((repo) => `${repo.owner}/${repo.name}`).join(", ")} (all ${selectedRepos.length} linked)` : `${selectedRepos.map((repo) => `${repo.owner}/${repo.name}`).join(", ")} (${selectedRepos.length} of ${projectDetail.linkedRepositories.length} linked)`;
830
+ const repoSummary = formatProjectRepoSummary(
831
+ selectedRepos,
832
+ projectDetail.linkedRepositories.length
833
+ );
483
834
  p.note(
484
835
  renderProjectRegistrationSummary({
485
836
  login,
@@ -516,10 +867,7 @@ async function projectAddInteractive(flags, options) {
516
867
  process.exitCode = 1;
517
868
  return;
518
869
  }
519
- p.outro(
520
- `Project "${projectId}" created.
521
- Run 'gh-symphony start' to begin orchestration.`
522
- );
870
+ p.outro(projectCreatedMessage(projectId, selectedRepos.length));
523
871
  }
524
872
  async function promptProjectRegistrationOptions(input) {
525
873
  const assignedOnly = await abortIfCancelled(
@@ -537,23 +885,25 @@ async function promptProjectRegistrationOptions(input) {
537
885
  let selectedRepos = input.projectDetail.linkedRepositories;
538
886
  let workspaceDir = input.defaultWorkspaceDir;
539
887
  if (customizeAdvancedOptions) {
540
- const filterRepositories = await abortIfCancelled(
541
- p.confirm({
542
- message: "Filter specific repositories? (default: No)",
543
- initialValue: false
544
- })
545
- );
546
- if (filterRepositories) {
547
- selectedRepos = await abortIfCancelled(
548
- p.multiselect({
549
- message: "Select repositories to orchestrate:",
550
- options: input.projectDetail.linkedRepositories.map((repo) => ({
551
- value: repo,
552
- label: `${repo.owner}/${repo.name}`
553
- })),
554
- required: true
888
+ if (input.projectDetail.linkedRepositories.length > 0) {
889
+ const filterRepositories = await abortIfCancelled(
890
+ p.confirm({
891
+ message: "Filter specific repositories? (default: No)",
892
+ initialValue: false
555
893
  })
556
894
  );
895
+ if (filterRepositories) {
896
+ selectedRepos = await abortIfCancelled(
897
+ p.multiselect({
898
+ message: "Select repositories to orchestrate:",
899
+ options: input.projectDetail.linkedRepositories.map((repo) => ({
900
+ value: repo,
901
+ label: `${repo.owner}/${repo.name}`
902
+ })),
903
+ required: true
904
+ })
905
+ );
906
+ }
557
907
  }
558
908
  workspaceDir = await abortIfCancelled(
559
909
  p.text({
@@ -632,7 +982,9 @@ async function projectRemove(args, options) {
632
982
  process.exitCode = 1;
633
983
  return;
634
984
  }
635
- const updatedProjects = global.projects.filter((entry) => entry !== projectId);
985
+ const updatedProjects = global.projects.filter(
986
+ (entry) => entry !== projectId
987
+ );
636
988
  if (updatedProjects.length === global.projects.length) {
637
989
  process.stderr.write(`Project "${projectId}" not found.
638
990
  `);
@@ -3,7 +3,7 @@ import {
3
3
  configFilePath,
4
4
  loadGlobalConfig,
5
5
  saveGlobalConfig
6
- } from "./chunk-ROGRTUFI.js";
6
+ } from "./chunk-QIRE2VXS.js";
7
7
 
8
8
  // src/commands/config-cmd.ts
9
9
  import { spawn } from "child_process";