@gh-symphony/cli 0.0.21 → 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 (38) hide show
  1. package/README.md +36 -0
  2. package/dist/{chunk-SXGT7LOF.js → chunk-2TSM3INR.js} +26 -1
  3. package/dist/{chunk-A67CMOYE.js → chunk-2UW7NQLX.js} +1 -1
  4. package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
  5. package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
  6. package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
  7. package/dist/{chunk-KY6WKH66.js → chunk-E7HYEEZD.js} +70 -52
  8. package/dist/{chunk-QEONJ5DZ.js → chunk-EEQQWTXS.js} +1288 -92
  9. package/dist/chunk-GDE6FYN4.js +26 -0
  10. package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
  11. package/dist/{chunk-JN3TQVFV.js → chunk-HMLBBZNY.js} +11 -2
  12. package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
  13. package/dist/{chunk-MYVJ6HK4.js → chunk-PUDXVBSN.js} +706 -376
  14. package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
  15. package/dist/{chunk-S6VIK4FF.js → chunk-ZHOKYUO3.js} +337 -13
  16. package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
  17. package/dist/{doctor-4HBRICHP.js → doctor-EJUMPBMW.js} +4 -4
  18. package/dist/index.js +88 -21
  19. package/dist/{init-HZ3JEDGQ.js → init-54HMKNYI.js} +3 -3
  20. package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
  21. package/dist/project-RMYMZSFV.js +25 -0
  22. package/dist/{recover-L3MJHHDA.js → recover-LTLKMTRX.js} +7 -7
  23. package/dist/repo-WI7GF6XQ.js +749 -0
  24. package/dist/{run-XJQ6BF7U.js → run-IHN3ZL35.js} +21 -9
  25. package/dist/{setup-B2SVLW2R.js → setup-TZJSM3QV.js} +14 -13
  26. package/dist/start-RTAHQMR2.js +19 -0
  27. package/dist/status-F4D52OVK.js +12 -0
  28. package/dist/stop-MDKMJPVR.js +10 -0
  29. package/dist/{upgrade-OJXPZRYE.js → upgrade-O33S2SJK.js} +2 -2
  30. package/dist/{version-TBDCTKDO.js → version-CW54Q7BK.js} +1 -1
  31. package/dist/worker-entry.js +369 -13
  32. package/dist/{workflow-BLJH2HC3.js → workflow-L3KT6HB7.js} +5 -5
  33. package/package.json +3 -3
  34. package/dist/project-25NQ4J4Y.js +0 -24
  35. package/dist/repo-TDCWQR6P.js +0 -379
  36. package/dist/start-I2CC7BLW.js +0 -18
  37. package/dist/status-QSCFVGRQ.js +0 -11
  38. package/dist/stop-7MFCBQVW.js +0 -9
package/README.md CHANGED
@@ -181,6 +181,7 @@ gh-symphony doctor # Validate local prerequisites, auth, confi
181
181
  gh-symphony doctor --fix # Apply safe fixes and guide/launch follow-up recovery commands
182
182
  gh-symphony project list # List all configured projects
183
183
  gh-symphony project remove <id> # Remove a project
184
+ gh-symphony project explain owner/repo#123 # Explain why one issue is not dispatching
184
185
  gh-symphony repo add owner/name # Validate and save a repo target manually
185
186
  gh-symphony repo sync # Add newly linked repositories from the GitHub Project
186
187
  gh-symphony repo sync --dry-run # Preview linked repository drift
@@ -201,6 +202,41 @@ removed, unchanged, and final repository sets.
201
202
 
202
203
  For empty projects, use `gh-symphony repo add owner/name` after setup to seed the local repository list without re-running the whole wizard.
203
204
 
205
+ ### Why Is My Issue Not Running?
206
+
207
+ Use `gh-symphony project explain <owner/repo#number>` before digging through
208
+ logs manually:
209
+
210
+ ```bash
211
+ gh-symphony project explain owner/repo#123
212
+ gh-symphony project explain owner/repo#123 --json
213
+ gh-symphony project explain owner/repo#123 --workflow ./WORKFLOW.md
214
+ ```
215
+
216
+ The command checks project repository linkage, GitHub Project item presence,
217
+ `WORKFLOW.md` active / wait / terminal state mapping, blocker state, existing
218
+ run / retry / convergence ownership, and project or per-state concurrency
219
+ limits.
220
+
221
+ If the project has no previous local run snapshot and the repository path is
222
+ not stored in the managed project config, pass `--workflow` so the command
223
+ does not guess from the current shell directory.
224
+
225
+ ```text
226
+ Issue dispatch explanation: owner/repo#123
227
+ Not dispatchable: Issue has 1 unresolved blocker.
228
+
229
+ Checks:
230
+ ✓ Repository owner/repo is linked to the active managed project.
231
+ ✓ Issue is present in the bound GitHub Project item set.
232
+ ✓ Project state "Todo" maps to an active state in WORKFLOW.md.
233
+ ✗ Issue has 1 unresolved blocker.
234
+ Hint: Move blocker issues to a terminal state or update the blocker relationship in GitHub.
235
+ ```
236
+
237
+ The remediation hints point to existing commands such as `workflow preview`,
238
+ `doctor`, `project status`, and `logs --issue`.
239
+
204
240
  ## 4. Run the Orchestrator
205
241
 
206
242
  ### Foreground
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_WORKFLOW_LIFECYCLE
4
- } from "./chunk-QEONJ5DZ.js";
4
+ } from "./chunk-EEQQWTXS.js";
5
5
 
6
6
  // ../tracker-github/src/adapter.ts
7
7
  import { createHash } from "crypto";
@@ -951,6 +951,19 @@ var githubProjectTrackerAdapter = {
951
951
  };
952
952
  }
953
953
  };
954
+ async function findGithubProjectIssue(project, identifier, dependencies = {}) {
955
+ const parsed = parseIssueIdentifier(identifier);
956
+ if (!parsed) {
957
+ return null;
958
+ }
959
+ const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
960
+ return fetchGithubProjectIssueByRepositoryAndNumber(
961
+ trackerConfig,
962
+ { owner: parsed.owner, name: parsed.name },
963
+ parsed.number,
964
+ dependencies.fetchImpl
965
+ );
966
+ }
954
967
  async function listProjectIssues(project, dependencies = {}) {
955
968
  const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
956
969
  const loadProjectIssues = () => fetchGithubProjectIssues(trackerConfig, dependencies.fetchImpl);
@@ -1053,8 +1066,20 @@ function parseIssueNumber(identifier) {
1053
1066
  const match = identifier.match(/#(\d+)$/);
1054
1067
  return match ? Number.parseInt(match[1] ?? "0", 10) : 0;
1055
1068
  }
1069
+ function parseIssueIdentifier(identifier) {
1070
+ const match = identifier.match(/^([^/\s#]+)\/([^/\s#]+)#(\d+)$/);
1071
+ if (!match) {
1072
+ return null;
1073
+ }
1074
+ return {
1075
+ owner: match[1],
1076
+ name: match[2],
1077
+ number: Number.parseInt(match[3], 10)
1078
+ };
1079
+ }
1056
1080
 
1057
1081
  export {
1058
1082
  fetchGithubProjectIssueByRepositoryAndNumber,
1083
+ findGithubProjectIssue,
1059
1084
  resolveTrackerAdapter
1060
1085
  };
@@ -8,7 +8,7 @@ import {
8
8
  resolveGitHubGraphQLToken,
9
9
  shouldReuseAgentCredentialCache,
10
10
  writeAgentCredentialCache
11
- } from "./chunk-QEONJ5DZ.js";
11
+ } from "./chunk-EEQQWTXS.js";
12
12
 
13
13
  // ../runtime-codex/src/runtime.ts
14
14
  import { spawn } from "child_process";
@@ -51,6 +51,14 @@ function showCursor() {
51
51
  return "\x1B[?25h";
52
52
  }
53
53
 
54
+ // src/format/repository.ts
55
+ function formatRepositoryDisplay(snapshot, fallback = "repository") {
56
+ if (snapshot.repository) {
57
+ return `${snapshot.repository.owner}/${snapshot.repository.name}`;
58
+ }
59
+ return snapshot.slug ?? fallback;
60
+ }
61
+
54
62
  export {
55
63
  bold,
56
64
  dim,
@@ -64,5 +72,6 @@ export {
64
72
  setNoColor,
65
73
  clearScreen,
66
74
  hideCursor,
67
- showCursor
75
+ showCursor,
76
+ formatRepositoryDisplay
68
77
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadGlobalConfig,
4
4
  loadProjectConfig
5
- } from "./chunk-ROGRTUFI.js";
5
+ } from "./chunk-QIRE2VXS.js";
6
6
 
7
7
  // src/project-selection.ts
8
8
  import * as p from "@clack/prompts";
@@ -5,6 +5,7 @@ import {
5
5
  clearScreen,
6
6
  cyan,
7
7
  dim,
8
+ formatRepositoryDisplay,
8
9
  green,
9
10
  hideCursor,
10
11
  magenta,
@@ -12,14 +13,17 @@ import {
12
13
  showCursor,
13
14
  stripAnsi,
14
15
  yellow
15
- } from "./chunk-MVRF7BES.js";
16
+ } from "./chunk-36KYEDEO.js";
16
17
  import {
17
18
  resolveRuntimeRoot
18
- } from "./chunk-5NV3LSAJ.js";
19
+ } from "./chunk-IWFX2FMA.js";
20
+ import {
21
+ rejectRemovedProjectId
22
+ } from "./chunk-GDE6FYN4.js";
19
23
  import {
20
24
  handleMissingManagedProjectConfig,
21
25
  resolveManagedProjectConfig
22
- } from "./chunk-C7G7RJ4G.js";
26
+ } from "./chunk-DDL4BWSL.js";
23
27
 
24
28
  // src/commands/status.ts
25
29
  import { readFile } from "fs/promises";
@@ -212,7 +216,9 @@ function renderDashboard(snapshots, options) {
212
216
  const hasActiveRuns = snap.activeRuns.length > 0;
213
217
  const hasRetries = snap.retryQueue.length > 0;
214
218
  if (!hasActiveRuns && !hasRetries) continue;
215
- lines.push(sectionDivider(snap.slug, width, c));
219
+ lines.push(
220
+ sectionDivider(formatRepositoryDisplay(snap), width, c)
221
+ );
216
222
  if (hasActiveRuns) {
217
223
  lines.push(tableHeaderRow(c));
218
224
  for (const rawRun of snap.activeRuns) {
@@ -280,7 +286,7 @@ function resolveProjectTokenDelta(snapshot) {
280
286
  function renderLegacyStatus(snapshot, noColor) {
281
287
  const apply = noColor ? (s) => stripAnsi(s) : (s) => s;
282
288
  const lines = [];
283
- const headerTitle = `gh-symphony \u2219 ${snapshot.slug}`;
289
+ const headerTitle = `gh-symphony \u2219 ${formatRepositoryDisplay(snapshot)}`;
284
290
  const headerWidth = 45;
285
291
  const headerPadding = Math.max(
286
292
  0,
@@ -363,16 +369,6 @@ function parseStatusArgs(args) {
363
369
  parsed.watch = true;
364
370
  continue;
365
371
  }
366
- if (arg === "--project" || arg === "--project-id") {
367
- const value = args[i + 1];
368
- if (!value || value.startsWith("-")) {
369
- parsed.error = `Option '${arg}' argument missing`;
370
- return parsed;
371
- }
372
- parsed.projectId = value;
373
- i += 1;
374
- continue;
375
- }
376
372
  if (arg?.startsWith("-")) {
377
373
  parsed.error = `Unknown option '${arg}'`;
378
374
  return parsed;
@@ -381,33 +377,33 @@ function parseStatusArgs(args) {
381
377
  return parsed;
382
378
  }
383
379
  async function readStatusSnapshot(runtimeRoot, projectId) {
384
- try {
385
- const statusPath = join(
386
- runtimeRoot,
387
- "projects",
388
- projectId,
389
- "status.json"
390
- );
391
- const content = await readFile(statusPath, "utf-8");
392
- return JSON.parse(content);
393
- } catch {
394
- return null;
380
+ for (const statusPath of [
381
+ join(runtimeRoot, "status.json"),
382
+ join(runtimeRoot, "projects", projectId, "status.json")
383
+ ]) {
384
+ try {
385
+ const content = await readFile(statusPath, "utf-8");
386
+ return JSON.parse(content);
387
+ } catch {
388
+ }
395
389
  }
390
+ return null;
396
391
  }
397
392
  var handler = async (args, options) => {
393
+ if (rejectRemovedProjectId(args)) {
394
+ return;
395
+ }
398
396
  const parsed = parseStatusArgs(args);
399
397
  if (parsed.error) {
400
398
  process.stderr.write(`${parsed.error}
401
399
  `);
402
- process.stderr.write(
403
- "Usage: gh-symphony status [--project-id <project-id>] [--watch]\n"
404
- );
400
+ process.stderr.write("Usage: gh-symphony status [--watch]\n");
405
401
  process.exitCode = 2;
406
402
  return;
407
403
  }
408
404
  const projectConfig = await resolveManagedProjectConfig({
409
405
  configDir: options.configDir,
410
- requestedProjectId: parsed.projectId
406
+ requestedProjectId: void 0
411
407
  });
412
408
  if (!projectConfig) {
413
409
  handleMissingManagedProjectConfig();
@@ -5,7 +5,7 @@ import {
5
5
  createStore,
6
6
  releaseProjectLock,
7
7
  resolveOrchestratorLogLevel
8
- } from "./chunk-MYVJ6HK4.js";
8
+ } from "./chunk-PUDXVBSN.js";
9
9
  import {
10
10
  getGhToken
11
11
  } from "./chunk-C67H3OUL.js";
@@ -17,29 +17,33 @@ import {
17
17
  parseRecentEvents,
18
18
  readJsonFile,
19
19
  safeReadDir
20
- } from "./chunk-QEONJ5DZ.js";
20
+ } from "./chunk-EEQQWTXS.js";
21
21
  import {
22
22
  bold,
23
23
  cyan,
24
24
  dim,
25
+ formatRepositoryDisplay,
25
26
  green,
26
27
  red,
27
28
  setNoColor,
28
29
  yellow
29
- } from "./chunk-MVRF7BES.js";
30
+ } from "./chunk-36KYEDEO.js";
30
31
  import {
31
32
  resolveRuntimeRoot
32
- } from "./chunk-5NV3LSAJ.js";
33
+ } from "./chunk-IWFX2FMA.js";
34
+ import {
35
+ rejectRemovedProjectId
36
+ } from "./chunk-GDE6FYN4.js";
33
37
  import {
34
38
  handleMissingManagedProjectConfig,
35
39
  resolveManagedProjectConfig
36
- } from "./chunk-C7G7RJ4G.js";
40
+ } from "./chunk-DDL4BWSL.js";
37
41
  import {
38
42
  daemonPidPath,
39
43
  httpStatusPath,
40
44
  orchestratorLogPath,
41
45
  writeJsonFile
42
- } from "./chunk-ROGRTUFI.js";
46
+ } from "./chunk-QIRE2VXS.js";
43
47
 
44
48
  // src/commands/start.ts
45
49
  import { writeFile, mkdir, readFile as readFile2, rm } from "fs/promises";
@@ -55,24 +59,30 @@ var RECENT_EVENT_CHUNK_SIZE = 4096;
55
59
  var MAX_RECENT_EVENT_SCAN_BYTES = 64 * 1024;
56
60
  var RUN_RECORD_LOAD_CONCURRENCY = 8;
57
61
  var DashboardFsReader = class {
58
- constructor(runtimeRoot, projectId) {
62
+ constructor(runtimeRoot) {
59
63
  this.runtimeRoot = runtimeRoot;
60
- this.projectId = projectId;
61
- assertValidDashboardProjectId(projectId);
62
64
  this.resolvedRuntimeRoot = resolve(runtimeRoot);
63
65
  }
64
66
  resolvedRuntimeRoot;
65
67
  projectDir() {
66
- return join(this.resolvedRuntimeRoot, "projects", this.projectId);
68
+ return this.resolvedRuntimeRoot;
67
69
  }
68
70
  runDir(runId) {
69
71
  assertValidDashboardRunId(runId);
70
- return join(this.projectDir(), "runs", runId);
72
+ return join(this.resolvedRuntimeRoot, "runs", runId);
71
73
  }
72
74
  async loadProjectStatus() {
73
- return readJsonFile(
74
- join(this.projectDir(), "status.json")
75
- );
75
+ const snapshot = await readJsonFile(join(this.projectDir(), "status.json"));
76
+ if (!snapshot) {
77
+ return null;
78
+ }
79
+ const status = { ...snapshot };
80
+ delete status.projectId;
81
+ delete status.slug;
82
+ if (!isRepositoryRef(status.repository)) {
83
+ return null;
84
+ }
85
+ return status;
76
86
  }
77
87
  async loadProjectState() {
78
88
  const snapshot = await this.loadProjectStatus();
@@ -216,7 +226,6 @@ async function statusForIssue(reader, issueIdentifier) {
216
226
  const currentRunCandidate = issueRecord.currentRunId ? await reader.loadRun(issueRecord.currentRunId) : null;
217
227
  const currentRun = isMatchingIssueRun(
218
228
  currentRunCandidate,
219
- reader.projectId,
220
229
  issueRecord.issueId,
221
230
  issueIdentifier
222
231
  ) ? currentRunCandidate : null;
@@ -300,13 +309,6 @@ function findLatestRunForIssue(matchingRuns) {
300
309
  );
301
310
  return sortedRuns[0] ?? null;
302
311
  }
303
- function assertValidDashboardProjectId(projectId) {
304
- if (projectId.length === 0 || projectId === "." || projectId === ".." || projectId.includes("/") || projectId.includes("\\")) {
305
- throw new Error(
306
- `Invalid project ID "${projectId}". Project IDs must not contain path separators or traversal segments.`
307
- );
308
- }
309
- }
310
312
  function assertValidDashboardRunId(runId) {
311
313
  if (runId.length === 0 || runId === "." || runId === ".." || runId.includes("/") || runId.includes("\\")) {
312
314
  throw new Error(
@@ -314,6 +316,13 @@ function assertValidDashboardRunId(runId) {
314
316
  );
315
317
  }
316
318
  }
319
+ function isRepositoryRef(value) {
320
+ if (!value || typeof value !== "object") {
321
+ return false;
322
+ }
323
+ const repository = value;
324
+ return typeof repository.owner === "string" && repository.owner.length > 0 && typeof repository.name === "string" && repository.name.length > 0 && typeof repository.cloneUrl === "string";
325
+ }
317
326
  async function mapWithConcurrency(items, concurrency, mapper) {
318
327
  const results = new Array(items.length);
319
328
  let nextIndex = 0;
@@ -507,7 +516,7 @@ function createControlPlaneHandler(options) {
507
516
  };
508
517
  }
509
518
  async function startControlPlaneServer(options) {
510
- const reader = new DashboardFsReader(options.runtimeRoot, options.projectId);
519
+ const reader = new DashboardFsReader(options.runtimeRoot);
511
520
  const handler2 = createControlPlaneHandler({
512
521
  reader,
513
522
  onRefreshRequest: options.onRefreshRequest
@@ -721,16 +730,6 @@ function parseStartArgs(args) {
721
730
  i += 1;
722
731
  continue;
723
732
  }
724
- if (arg === "--project" || arg === "--project-id") {
725
- const value = args[i + 1];
726
- if (!value || value.startsWith("-")) {
727
- parsed.error = `Option '${arg}' argument missing`;
728
- return parsed;
729
- }
730
- parsed.projectId = value;
731
- i += 1;
732
- continue;
733
- }
734
733
  if (arg === "--log-level") {
735
734
  const value = args[i + 1];
736
735
  if (!value || value.startsWith("-")) {
@@ -756,7 +755,9 @@ function logTickResult(snapshot, prevSnapshot, isFirst) {
756
755
  const healthColor = snapshot.health === "degraded" ? red : snapshot.health === "running" ? green : cyan;
757
756
  logLine(
758
757
  green("\u25CF"),
759
- `Project ${bold(snapshot.slug)} connected ${dim("(")}${healthColor(snapshot.health)}${dim(")")}`
758
+ `Repository ${bold(formatRepositoryDisplay(snapshot))} connected ${dim(
759
+ "("
760
+ )}${healthColor(snapshot.health)}${dim(")")}`
760
761
  );
761
762
  if (snapshot.summary.activeRuns > 0) {
762
763
  logLine(cyan("\u25B8"), `${snapshot.summary.activeRuns} active run(s)`);
@@ -885,7 +886,7 @@ async function removeHttpBindingState(configDir, projectId) {
885
886
  await rm(httpStatusPath(configDir, projectId), { force: true });
886
887
  }
887
888
  async function startHttpServer(input) {
888
- const reader = new DashboardFsReader(input.runtimeRoot, input.projectId);
889
+ const reader = new DashboardFsReader(input.runtimeRoot);
889
890
  for (let port = input.initialPort; port <= 65535; port += 1) {
890
891
  const server = createServer3((request, response) => {
891
892
  void (async () => {
@@ -955,6 +956,9 @@ var handler = async (args, options) => {
955
956
  setNoColor(options.noColor);
956
957
  let parsed;
957
958
  try {
959
+ if (rejectRemovedProjectId(args)) {
960
+ return;
961
+ }
958
962
  parsed = parseStartArgs(args);
959
963
  } catch (error) {
960
964
  process.stderr.write(
@@ -968,24 +972,33 @@ var handler = async (args, options) => {
968
972
  process.stderr.write(`${parsed.error}
969
973
  `);
970
974
  process.stderr.write(
971
- "Usage: gh-symphony start --project-id <project-id> [--daemon] [--once] [--http [port]] [--web [port]]\n"
975
+ "Usage: gh-symphony start [--daemon] [--once] [--http [port]] [--web [port]]\n"
972
976
  );
973
977
  process.exitCode = 2;
974
978
  return;
975
979
  }
976
980
  if (parsed.daemon && parsed.once) {
977
- process.stderr.write("Options '--daemon' and '--once' cannot be used together\n");
981
+ process.stderr.write(
982
+ "Options '--daemon' and '--once' cannot be used together\n"
983
+ );
978
984
  process.exitCode = 2;
979
985
  return;
980
986
  }
981
987
  const projectConfig = await resolveManagedProjectConfig({
982
988
  configDir: options.configDir,
983
- requestedProjectId: parsed.projectId
989
+ requestedProjectId: void 0
984
990
  });
985
991
  if (!projectConfig) {
986
992
  handleMissingManagedProjectConfig();
987
993
  return;
988
994
  }
995
+ if (!hasConfiguredRepository(projectConfig)) {
996
+ process.stderr.write(
997
+ "No repository is configured in this project. Run 'gh-symphony repo add owner/name' first.\n"
998
+ );
999
+ process.exitCode = 1;
1000
+ return;
1001
+ }
989
1002
  const runtimeRoot = resolveRuntimeRoot(options.configDir);
990
1003
  const projectId = projectConfig.projectId;
991
1004
  let logLevel;
@@ -1094,7 +1107,6 @@ var handler = async (args, options) => {
1094
1107
  host: HTTP_HOST,
1095
1108
  port: parsed.webPort,
1096
1109
  runtimeRoot,
1097
- projectId,
1098
1110
  onRefreshRequest: () => service.requestReconcile()
1099
1111
  }) : parsed.httpPort !== void 0 ? await startHttpServer({
1100
1112
  runtimeRoot,
@@ -1238,19 +1250,27 @@ async function shutdownForegroundOrchestrator(input) {
1238
1250
  }
1239
1251
  return (input.exit ?? process.exit)(0);
1240
1252
  }
1253
+ function hasConfiguredRepository(config) {
1254
+ return Boolean(config.repository?.owner && config.repository.name);
1255
+ }
1241
1256
  async function tailWorkerLog(runtimeRoot, projectId, runId, issueIdentifier) {
1242
- try {
1243
- const logPath = join3(runtimeRoot, "projects", projectId, "runs", runId, "worker.log");
1244
- const content = await readFile2(logPath, "utf8");
1245
- const lines = content.split("\n").filter((l) => l.trim());
1246
- if (lines.length === 0) return;
1247
- const tail = lines.slice(-30);
1248
- logLine(red("\u2717"), red(`Worker stderr (${issueIdentifier}):`));
1249
- for (const line of tail) {
1250
- process.stdout.write(` ${dim(line)}
1257
+ for (const logPath of [
1258
+ join3(runtimeRoot, "runs", runId, "worker.log"),
1259
+ join3(runtimeRoot, "projects", projectId, "runs", runId, "worker.log")
1260
+ ]) {
1261
+ try {
1262
+ const content = await readFile2(logPath, "utf8");
1263
+ const lines = content.split("\n").filter((l) => l.trim());
1264
+ if (lines.length === 0) return;
1265
+ const tail = lines.slice(-30);
1266
+ logLine(red("\u2717"), red(`Worker stderr (${issueIdentifier}):`));
1267
+ for (const line of tail) {
1268
+ process.stdout.write(` ${dim(line)}
1251
1269
  `);
1270
+ }
1271
+ return;
1272
+ } catch {
1252
1273
  }
1253
- } catch {
1254
1274
  }
1255
1275
  }
1256
1276
  var start_default = handler;
@@ -1264,8 +1284,6 @@ async function startDaemon(options, projectId, logLevel, httpPort, webPort) {
1264
1284
  [
1265
1285
  process.argv[1],
1266
1286
  "start",
1267
- "--project",
1268
- projectId,
1269
1287
  ...httpPort !== void 0 ? ["--http", String(httpPort)] : [],
1270
1288
  ...webPort !== void 0 ? ["--web", String(webPort)] : [],
1271
1289
  ...logLevel ? ["--log-level", logLevel] : []
@@ -1289,7 +1307,7 @@ async function startDaemon(options, projectId, logLevel, httpPort, webPort) {
1289
1307
  process.stdout.write(
1290
1308
  `Orchestrator started in background (PID: ${child.pid}).
1291
1309
  Logs: ${logPath}
1292
- Stop with: gh-symphony project stop --project-id ${projectId}
1310
+ Stop with: gh-symphony repo stop
1293
1311
  `
1294
1312
  );
1295
1313
  }