@gh-symphony/cli 0.0.22 → 0.1.3

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 (36) hide show
  1. package/README.md +72 -77
  2. package/dist/{chunk-HMLBBZNY.js → chunk-2YF7PQUC.js} +16 -71
  3. package/dist/{chunk-IWFX2FMA.js → chunk-6I753NYO.js} +4 -1
  4. package/dist/{chunk-2TSM3INR.js → chunk-HQ7A3C7K.js} +575 -12
  5. package/dist/{chunk-36KYEDEO.js → chunk-MVRF7BES.js} +1 -10
  6. package/dist/{workflow-L3KT6HB7.js → chunk-NESHTYXQ.js} +27 -19
  7. package/dist/{chunk-2UW7NQLX.js → chunk-PEZUBHWJ.js} +1 -1
  8. package/dist/chunk-PG332ZS4.js +238 -0
  9. package/dist/{chunk-EEQQWTXS.js → chunk-WCOIVNHH.js} +213 -82
  10. package/dist/{chunk-QIRE2VXS.js → chunk-WOVNN5NW.js} +16 -17
  11. package/dist/{chunk-C67H3OUL.js → chunk-Z3NZOPLZ.js} +0 -81
  12. package/dist/{config-cmd-Z3A7V6NC.js → config-cmd-2ADPUYWA.js} +1 -1
  13. package/dist/{doctor-EJUMPBMW.js → doctor-2AXHIEAP.js} +464 -40
  14. package/dist/index.js +340 -294
  15. package/dist/{chunk-PUDXVBSN.js → repo-SUXYT4OK.js} +6272 -2996
  16. package/dist/{setup-TZJSM3QV.js → setup-UBHOMXUG.js} +57 -92
  17. package/dist/{upgrade-O33S2SJK.js → upgrade-355SQJ5P.js} +2 -2
  18. package/dist/{version-CW54Q7BK.js → version-4ILSDZQH.js} +1 -1
  19. package/dist/worker-entry.js +10 -5
  20. package/dist/workflow-S6YSZPQT.js +22 -0
  21. package/package.json +4 -4
  22. package/dist/chunk-DDL4BWSL.js +0 -146
  23. package/dist/chunk-DFLXHNYQ.js +0 -482
  24. package/dist/chunk-E7HYEEZD.js +0 -1318
  25. package/dist/chunk-GDE6FYN4.js +0 -26
  26. package/dist/chunk-GSX2FV3M.js +0 -103
  27. package/dist/chunk-ZHOKYUO3.js +0 -1047
  28. package/dist/init-54HMKNYI.js +0 -38
  29. package/dist/logs-GTZ4U5JE.js +0 -188
  30. package/dist/project-RMYMZSFV.js +0 -25
  31. package/dist/recover-LTLKMTRX.js +0 -133
  32. package/dist/repo-WI7GF6XQ.js +0 -749
  33. package/dist/run-IHN3ZL35.js +0 -122
  34. package/dist/start-RTAHQMR2.js +0 -19
  35. package/dist/status-F4D52OVK.js +0 -12
  36. package/dist/stop-MDKMJPVR.js +0 -10
@@ -25,6 +25,7 @@ function normalizeWorkflowState(state) {
25
25
  var DEFAULT_CODEX_COMMAND = "codex app-server";
26
26
  var DEFAULT_CLAUDE_COMMAND = "claude";
27
27
  var DEFAULT_AGENT_COMMAND = DEFAULT_CODEX_COMMAND;
28
+ var DEFAULT_LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
28
29
  var DEFAULT_HOOK_TIMEOUT_MS = 6e4;
29
30
  var DEFAULT_POLL_INTERVAL_MS = 3e4;
30
31
  var DEFAULT_MAX_RETRY_BACKOFF_MS = 3e5;
@@ -129,6 +130,7 @@ function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
129
130
  const hasRuntime = runtimeNode !== null;
130
131
  const codex = hasRuntime ? readObject(frontMatter, "codex") : readRequiredObject(frontMatter, "codex");
131
132
  const trackerKind = readRequiredString(tracker, "kind", env);
133
+ validateTrackerConfig(tracker, trackerKind, env);
132
134
  const activeStates = readStringList(tracker, "active_states") ?? DEFAULT_WORKFLOW_TRACKER.activeStates;
133
135
  const terminalStates = readStringList(tracker, "terminal_states") ?? DEFAULT_WORKFLOW_TRACKER.terminalStates;
134
136
  const blockerCheckStates = readStringList(tracker, "blocker_check_states") ?? DEFAULT_WORKFLOW_TRACKER.blockerCheckStates;
@@ -160,7 +162,7 @@ function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
160
162
  ),
161
163
  tracker: {
162
164
  kind: trackerKind,
163
- endpoint: readOptionalString(tracker, "endpoint", env),
165
+ endpoint: readOptionalString(tracker, "endpoint", env) ?? (trackerKind === "linear" ? DEFAULT_LINEAR_GRAPHQL_URL : null),
164
166
  apiKey: readOptionalString(tracker, "api_key", env),
165
167
  projectSlug: readOptionalString(tracker, "project_slug", env),
166
168
  activeStates,
@@ -207,6 +209,32 @@ function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
207
209
  };
208
210
  return parsed;
209
211
  }
212
+ function validateTrackerConfig(tracker, trackerKind, env) {
213
+ if (trackerKind !== "linear") {
214
+ return;
215
+ }
216
+ for (const key of ["project_id", "projectId", "teamId", "team_id"]) {
217
+ if (key in tracker) {
218
+ throw new Error(
219
+ `Workflow front matter field "tracker.${key}" is not supported for tracker.kind "linear"; use "tracker.project_slug".`
220
+ );
221
+ }
222
+ }
223
+ const projectSlug = readOptionalString(tracker, "project_slug", env);
224
+ if (!projectSlug || projectSlug.trim().length === 0) {
225
+ throw new Error(
226
+ 'Workflow front matter field "tracker.project_slug" is required for tracker.kind "linear".'
227
+ );
228
+ }
229
+ if ("endpoint" in tracker) {
230
+ const endpoint = readOptionalString(tracker, "endpoint", env);
231
+ if (!endpoint || endpoint.trim().length === 0) {
232
+ throw new Error(
233
+ 'Workflow front matter field "tracker.endpoint" must be a non-empty string when provided for tracker.kind "linear".'
234
+ );
235
+ }
236
+ }
237
+ }
210
238
  function parseLegacyWorkflowMarkdown(markdown) {
211
239
  const promptGuidelines = matchOptionalSection(markdown, "Prompt Guidelines") ?? "";
212
240
  return {
@@ -372,7 +400,9 @@ function splitInlineArrayEntries(inner) {
372
400
  current += char;
373
401
  }
374
402
  if (quote) {
375
- throw new Error("Workflow front matter inline array has an unterminated string.");
403
+ throw new Error(
404
+ "Workflow front matter inline array has an unterminated string."
405
+ );
376
406
  }
377
407
  pushInlineArrayEntry(entries, current, "end");
378
408
  return entries;
@@ -564,6 +594,16 @@ function resolveEnvironmentValue(value, env) {
564
594
  }
565
595
  return resolved;
566
596
  }
597
+ const dollarEnvTokenMatch = value.match(/^\$([A-Z0-9_]+)$/);
598
+ if (dollarEnvTokenMatch) {
599
+ const resolved = env[dollarEnvTokenMatch[1]];
600
+ if (!resolved) {
601
+ throw new Error(
602
+ `Workflow front matter requires environment variable ${dollarEnvTokenMatch[1]}.`
603
+ );
604
+ }
605
+ return resolved;
606
+ }
567
607
  return value.replace(/\$\{([A-Z0-9_]+)\}/g, (_, name) => {
568
608
  const resolved = env[name];
569
609
  if (!resolved) {
@@ -583,73 +623,6 @@ function matchOptionalSection(markdown, heading) {
583
623
  return match?.[1]?.trim() ?? null;
584
624
  }
585
625
 
586
- // ../core/src/workflow/loader.ts
587
- import { createHash } from "crypto";
588
- import { access, readFile, stat } from "fs/promises";
589
- import { constants } from "fs";
590
- var WorkflowConfigStore = class {
591
- cache = /* @__PURE__ */ new Map();
592
- async load(workflowPath, env = process.env) {
593
- await access(workflowPath, constants.R_OK);
594
- const fileStat = await stat(workflowPath);
595
- const cached = this.cache.get(workflowPath);
596
- const markdown = await readFile(workflowPath, "utf8");
597
- const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
598
- if (cached && cached.fingerprint === fingerprint) {
599
- return toWorkflowResolution(workflowPath, cached.workflow, {
600
- isValid: true,
601
- usedLastKnownGood: false,
602
- validationError: null
603
- });
604
- }
605
- try {
606
- const workflow = parseWorkflowMarkdown(markdown, env);
607
- this.cache.set(workflowPath, {
608
- fingerprint,
609
- workflow,
610
- loadedAt: (/* @__PURE__ */ new Date()).toISOString()
611
- });
612
- return toWorkflowResolution(workflowPath, workflow, {
613
- isValid: true,
614
- usedLastKnownGood: false,
615
- validationError: null
616
- });
617
- } catch (error) {
618
- if (cached) {
619
- return toWorkflowResolution(workflowPath, cached.workflow, {
620
- isValid: false,
621
- usedLastKnownGood: true,
622
- validationError: error instanceof Error ? error.message : "Invalid workflow definition."
623
- });
624
- }
625
- throw error;
626
- }
627
- }
628
- };
629
- function createDefaultWorkflowResolution() {
630
- return createInvalidWorkflowResolution(null, "missing_workflow_file");
631
- }
632
- function createInvalidWorkflowResolution(workflowPath, validationError) {
633
- return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
634
- isValid: false,
635
- usedLastKnownGood: false,
636
- validationError
637
- });
638
- }
639
- function toWorkflowResolution(workflowPath, workflow, metadata) {
640
- return {
641
- workflowPath,
642
- workflow,
643
- lifecycle: workflow.lifecycle,
644
- promptTemplate: workflow.promptTemplate,
645
- agentCommand: workflow.agentCommand,
646
- hookPath: workflow.hookPath ?? "",
647
- isValid: metadata.isValid,
648
- usedLastKnownGood: metadata.usedLastKnownGood,
649
- validationError: metadata.validationError
650
- };
651
- }
652
-
653
626
  // ../core/src/workflow/render.ts
654
627
  import {
655
628
  Liquid,
@@ -659,6 +632,17 @@ import {
659
632
  UndefinedVariableError
660
633
  } from "liquidjs";
661
634
  function buildPromptVariables(issue, options) {
635
+ const contentType = issue.metadata.contentType ?? "Issue";
636
+ const linkedPullRequests = Array.isArray(issue.metadata.linkedPullRequests) ? issue.metadata.linkedPullRequests.map(normalizePullRequestContext) : [];
637
+ const primaryPullRequest = contentType === "PullRequest" ? normalizePullRequestContext(
638
+ issue.metadata.pullRequest ?? linkedPullRequests[0] ?? buildPullRequestContextFromIssue(issue)
639
+ ) : linkedPullRequests[0] ?? null;
640
+ const pullRequestContext = buildPullRequestVariables({
641
+ contentType,
642
+ linkedPullRequests,
643
+ primaryPullRequest,
644
+ issueBranchName: issue.branchName
645
+ });
662
646
  return {
663
647
  issue: {
664
648
  id: issue.id,
@@ -672,13 +656,61 @@ function buildPromptVariables(issue, options) {
672
656
  labels: issue.labels,
673
657
  blocked_by: issue.blockedBy,
674
658
  branch_name: issue.branchName,
659
+ content_type: contentType,
660
+ linked_pull_requests: linkedPullRequests,
661
+ primary_pull_request: primaryPullRequest,
662
+ has_linked_pr: linkedPullRequests.length > 0,
675
663
  created_at: issue.createdAt,
676
664
  updated_at: issue.updatedAt,
677
665
  repository: `${issue.repository.owner}/${issue.repository.name}`
678
666
  },
667
+ pull_request_context: pullRequestContext,
679
668
  attempt: options.attempt
680
669
  };
681
670
  }
671
+ function normalizePullRequestContext(pullRequest) {
672
+ return {
673
+ ...pullRequest,
674
+ state: pullRequest.state ?? null,
675
+ projectState: pullRequest.projectState ?? null,
676
+ isDraft: pullRequest.isDraft ?? null,
677
+ merged: pullRequest.merged ?? null,
678
+ headRefName: pullRequest.headRefName ?? null,
679
+ baseRefName: pullRequest.baseRefName ?? null
680
+ };
681
+ }
682
+ function buildPullRequestVariables(options) {
683
+ const checkoutBranch = options.primaryPullRequest?.headRefName ?? (options.contentType === "PullRequest" ? options.issueBranchName : null);
684
+ const hasPrimaryPr = options.primaryPullRequest !== null;
685
+ return {
686
+ subject_type: options.contentType,
687
+ linked_pull_requests: options.linkedPullRequests,
688
+ primary_pull_request: options.primaryPullRequest,
689
+ has_linked_pr: options.linkedPullRequests.length > 0,
690
+ has_primary_pr: hasPrimaryPr,
691
+ checkout_branch: checkoutBranch,
692
+ // Policy flag for prompt templates: any primary PR means the worker must
693
+ // inspect review threads and checks before editing code.
694
+ review_first: hasPrimaryPr
695
+ };
696
+ }
697
+ function buildPullRequestContextFromIssue(issue) {
698
+ return {
699
+ id: issue.id,
700
+ number: issue.number,
701
+ identifier: issue.identifier,
702
+ url: issue.url,
703
+ state: null,
704
+ projectState: issue.state,
705
+ headRefName: issue.branchName,
706
+ repository: {
707
+ owner: issue.repository.owner,
708
+ name: issue.repository.name,
709
+ url: issue.repository.url ?? "",
710
+ cloneUrl: issue.repository.cloneUrl
711
+ }
712
+ };
713
+ }
682
714
  var STRICT_LIQUID_ENGINE = new Liquid({
683
715
  strictVariables: true,
684
716
  strictFilters: true,
@@ -873,6 +905,73 @@ function isOrchestratorChannelEvent(value) {
873
905
  return false;
874
906
  }
875
907
 
908
+ // ../core/src/workflow/loader.ts
909
+ import { createHash } from "crypto";
910
+ import { access, readFile, stat } from "fs/promises";
911
+ import { constants } from "fs";
912
+ var WorkflowConfigStore = class {
913
+ cache = /* @__PURE__ */ new Map();
914
+ async load(workflowPath, env = process.env) {
915
+ await access(workflowPath, constants.R_OK);
916
+ const fileStat = await stat(workflowPath);
917
+ const cached = this.cache.get(workflowPath);
918
+ const markdown = await readFile(workflowPath, "utf8");
919
+ const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
920
+ if (cached && cached.fingerprint === fingerprint) {
921
+ return toWorkflowResolution(workflowPath, cached.workflow, {
922
+ isValid: true,
923
+ usedLastKnownGood: false,
924
+ validationError: null
925
+ });
926
+ }
927
+ try {
928
+ const workflow = parseWorkflowMarkdown(markdown, env);
929
+ this.cache.set(workflowPath, {
930
+ fingerprint,
931
+ workflow,
932
+ loadedAt: (/* @__PURE__ */ new Date()).toISOString()
933
+ });
934
+ return toWorkflowResolution(workflowPath, workflow, {
935
+ isValid: true,
936
+ usedLastKnownGood: false,
937
+ validationError: null
938
+ });
939
+ } catch (error) {
940
+ if (cached) {
941
+ return toWorkflowResolution(workflowPath, cached.workflow, {
942
+ isValid: false,
943
+ usedLastKnownGood: true,
944
+ validationError: error instanceof Error ? error.message : "Invalid workflow definition."
945
+ });
946
+ }
947
+ throw error;
948
+ }
949
+ }
950
+ };
951
+ function createDefaultWorkflowResolution() {
952
+ return createInvalidWorkflowResolution(null, "missing_workflow_file");
953
+ }
954
+ function createInvalidWorkflowResolution(workflowPath, validationError) {
955
+ return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
956
+ isValid: false,
957
+ usedLastKnownGood: false,
958
+ validationError
959
+ });
960
+ }
961
+ function toWorkflowResolution(workflowPath, workflow, metadata) {
962
+ return {
963
+ workflowPath,
964
+ workflow,
965
+ lifecycle: workflow.lifecycle,
966
+ promptTemplate: workflow.promptTemplate,
967
+ agentCommand: workflow.agentCommand,
968
+ hookPath: workflow.hookPath ?? "",
969
+ isValid: metadata.isValid,
970
+ usedLastKnownGood: metadata.usedLastKnownGood,
971
+ validationError: metadata.validationError
972
+ };
973
+ }
974
+
876
975
  // ../core/src/workflow/exit-classification.ts
877
976
  function classifySessionExit(params) {
878
977
  if (params.userInputRequired) {
@@ -1471,7 +1570,7 @@ async function runClaudePreflight(options, dependencies = {}) {
1471
1570
  const command = resolveRuntimeCommandBinary(options.command) ?? "claude";
1472
1571
  const checks = [];
1473
1572
  checks.push(checkClaudeBinary(command, options.cwd, deps));
1474
- checks.push(await checkAnthropicApiKey(env, options, deps));
1573
+ checks.push(await checkClaudeAuthentication(env, options, deps));
1475
1574
  checks.push(await checkWorkspaceMcpConfig(options.cwd, deps));
1476
1575
  if (options.includeGhAuth) {
1477
1576
  checks.push(checkGhAuthentication(options.cwd, deps));
@@ -1573,11 +1672,12 @@ function checkGhAuthentication(cwd, deps) {
1573
1672
  );
1574
1673
  }
1575
1674
  }
1576
- async function checkAnthropicApiKey(env, options, deps) {
1675
+ async function checkClaudeAuthentication(env, options, deps) {
1676
+ const authMode = options.authMode ?? "api-key-required";
1577
1677
  if (env.ANTHROPIC_API_KEY?.trim()) {
1578
1678
  return pass(
1579
1679
  "anthropic_api_key",
1580
- "Anthropic API key",
1680
+ "Claude authentication",
1581
1681
  "ANTHROPIC_API_KEY is configured in the environment.",
1582
1682
  { source: "env" }
1583
1683
  );
@@ -1585,9 +1685,32 @@ async function checkAnthropicApiKey(env, options, deps) {
1585
1685
  const brokerUrl = env.AGENT_CREDENTIAL_BROKER_URL?.trim();
1586
1686
  const brokerSecret = env.AGENT_CREDENTIAL_BROKER_SECRET?.trim();
1587
1687
  if (!brokerUrl || !brokerSecret) {
1688
+ if (authMode === "local-or-api-key" && !brokerUrl && !brokerSecret) {
1689
+ return warn(
1690
+ "anthropic_api_key",
1691
+ "Claude authentication",
1692
+ "ANTHROPIC_API_KEY and agent credential broker are not configured; Claude Code local login may be used for this non-bare runtime.",
1693
+ "If this runtime will run headlessly or with runtime.isolation.bare: true, set ANTHROPIC_API_KEY or configure AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1694
+ { source: "local" }
1695
+ );
1696
+ }
1697
+ if (brokerUrl || brokerSecret) {
1698
+ const missingName = brokerUrl ? "AGENT_CREDENTIAL_BROKER_SECRET" : "AGENT_CREDENTIAL_BROKER_URL";
1699
+ return fail(
1700
+ "anthropic_api_key",
1701
+ "Claude authentication",
1702
+ `Agent credential broker configuration is incomplete: ${missingName} is not configured.`,
1703
+ `Set ${missingName} or remove the partial broker configuration and use ANTHROPIC_API_KEY instead.`,
1704
+ {
1705
+ source: "broker",
1706
+ brokerUrl: brokerUrl ?? null,
1707
+ missing: missingName
1708
+ }
1709
+ );
1710
+ }
1588
1711
  return fail(
1589
1712
  "anthropic_api_key",
1590
- "Anthropic API key",
1713
+ "Claude authentication",
1591
1714
  "Neither ANTHROPIC_API_KEY nor an agent credential broker is configured.",
1592
1715
  "Set ANTHROPIC_API_KEY or configure AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1593
1716
  { source: "missing" }
@@ -1596,7 +1719,7 @@ async function checkAnthropicApiKey(env, options, deps) {
1596
1719
  if (options.probeCredentialBroker === false) {
1597
1720
  return pass(
1598
1721
  "anthropic_api_key",
1599
- "Anthropic API key",
1722
+ "Claude authentication",
1600
1723
  "Agent credential broker configuration is present.",
1601
1724
  { source: "broker", brokerUrl }
1602
1725
  );
@@ -1614,14 +1737,14 @@ async function checkAnthropicApiKey(env, options, deps) {
1614
1737
  if (response.ok && payload.env?.ANTHROPIC_API_KEY?.trim()) {
1615
1738
  return pass(
1616
1739
  "anthropic_api_key",
1617
- "Anthropic API key",
1740
+ "Claude authentication",
1618
1741
  "Agent credential broker is reachable and returned ANTHROPIC_API_KEY.",
1619
1742
  { source: "broker", brokerUrl }
1620
1743
  );
1621
1744
  }
1622
1745
  return fail(
1623
1746
  "anthropic_api_key",
1624
- "Anthropic API key",
1747
+ "Claude authentication",
1625
1748
  payload.error ? `Agent credential broker did not return ANTHROPIC_API_KEY: ${payload.error}.` : "Agent credential broker did not return ANTHROPIC_API_KEY.",
1626
1749
  "Set ANTHROPIC_API_KEY or configure the credential broker to return ANTHROPIC_API_KEY.",
1627
1750
  { source: "broker", brokerUrl, status: response.status }
@@ -1629,7 +1752,7 @@ async function checkAnthropicApiKey(env, options, deps) {
1629
1752
  } catch (error) {
1630
1753
  return fail(
1631
1754
  "anthropic_api_key",
1632
- "Anthropic API key",
1755
+ "Claude authentication",
1633
1756
  "Agent credential broker could not be reached.",
1634
1757
  "Set ANTHROPIC_API_KEY or fix AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1635
1758
  {
@@ -2105,15 +2228,18 @@ function createGitHubGraphQLMcpServerEntry(options = {}) {
2105
2228
  // ../runtime-claude/src/mcp-compose.ts
2106
2229
  async function composeClaudeMcpConfig(workspaceRoot, strictMode, symphonyTokenEnv = {}) {
2107
2230
  const workspaceMcpPath = join3(workspaceRoot, ".mcp.json");
2108
- const finalPath = strictMode ? resolveStrictMcpConfigPath(workspaceRoot, symphonyTokenEnv) : workspaceMcpPath;
2231
+ const finalPath = resolveRuntimeMcpConfigPath(
2232
+ workspaceRoot,
2233
+ symphonyTokenEnv
2234
+ );
2109
2235
  const baseConfig = await readBaseMcpConfig(workspaceMcpPath);
2110
2236
  const mergedConfig = mergeGitHubGraphQLMcpServer(baseConfig, symphonyTokenEnv);
2111
2237
  await mkdir(dirname(finalPath), { recursive: true });
2112
2238
  await writeFile3(finalPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf8");
2113
2239
  return {
2114
2240
  finalPath,
2115
- extraArgv: strictMode ? ["--strict-mcp-config", "--mcp-config", finalPath] : [],
2116
- ...strictMode ? { cleanupPath: finalPath } : {}
2241
+ extraArgv: strictMode ? ["--strict-mcp-config", "--mcp-config", finalPath] : ["--mcp-config", finalPath],
2242
+ cleanupPath: finalPath
2117
2243
  };
2118
2244
  }
2119
2245
  async function readBaseMcpConfig(workspaceMcpPath) {
@@ -2145,9 +2271,13 @@ function mergeGitHubGraphQLMcpServer(baseConfig, env) {
2145
2271
  }
2146
2272
  };
2147
2273
  }
2148
- function resolveStrictMcpConfigPath(workspaceRoot, env) {
2274
+ function resolveRuntimeMcpConfigPath(workspaceRoot, env) {
2149
2275
  const normalizedWorkspaceRoot = resolve4(workspaceRoot);
2150
- const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join3(normalizedWorkspaceRoot, ".runtime", basename(normalizedWorkspaceRoot));
2276
+ const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join3(
2277
+ dirname(normalizedWorkspaceRoot),
2278
+ ".runtime",
2279
+ basename(normalizedWorkspaceRoot)
2280
+ );
2151
2281
  return join3(runtimeDir, "mcp.json");
2152
2282
  }
2153
2283
  function isRecord3(value) {
@@ -3214,6 +3344,7 @@ export {
3214
3344
  isStateActive,
3215
3345
  isStateTerminal,
3216
3346
  matchesWorkflowState,
3347
+ DEFAULT_LINEAR_GRAPHQL_URL,
3217
3348
  DEFAULT_MAX_FAILURE_RETRIES,
3218
3349
  resolveWorkflowRuntimeCommand,
3219
3350
  resolveWorkflowRuntimeTimeouts,
@@ -1,16 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/config.ts
4
+ import { existsSync } from "fs";
4
5
  import { readFile, writeFile, mkdir } from "fs/promises";
5
- import { dirname, join } from "path";
6
+ import { dirname, join, resolve } from "path";
6
7
  import { homedir } from "os";
7
8
  var DEFAULT_CONFIG_DIR = join(homedir(), ".gh-symphony");
8
9
  var CONFIG_FILE = "config.json";
9
10
  var DAEMON_PID_FILE = "daemon.pid";
10
11
  var ORCHESTRATOR_LOG_FILE = "orchestrator.log";
11
12
  var HTTP_STATUS_FILE = "http.json";
13
+ var REPO_RUNTIME_DIR = join(".runtime", "orchestrator");
12
14
  function resolveConfigDir(override) {
13
- return override ?? process.env.GH_SYMPHONY_CONFIG_DIR ?? DEFAULT_CONFIG_DIR;
15
+ if (override) {
16
+ return override;
17
+ }
18
+ if (process.env.GH_SYMPHONY_CONFIG_DIR) {
19
+ return process.env.GH_SYMPHONY_CONFIG_DIR;
20
+ }
21
+ const repoRuntimeDir = resolve(process.cwd(), REPO_RUNTIME_DIR);
22
+ if (existsSync(configFilePath(repoRuntimeDir))) {
23
+ return repoRuntimeDir;
24
+ }
25
+ return DEFAULT_CONFIG_DIR;
14
26
  }
15
27
  function configFilePath(configDir) {
16
28
  return join(configDir, CONFIG_FILE);
@@ -54,15 +66,7 @@ async function saveGlobalConfig(configDir, config) {
54
66
  await writeJsonFile(configFilePath(configDir), config);
55
67
  }
56
68
  async function loadProjectConfig(configDir, projectId) {
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
- };
69
+ return readJsonFile(projectConfigPath(configDir, projectId));
66
70
  }
67
71
  async function saveProjectConfig(configDir, projectId, config) {
68
72
  await writeJsonFile(projectConfigPath(configDir, projectId), config);
@@ -97,16 +101,11 @@ function isFileMissing(error) {
97
101
  error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
98
102
  );
99
103
  }
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
- }
105
104
 
106
105
  export {
106
+ REPO_RUNTIME_DIR,
107
107
  resolveConfigDir,
108
108
  configFilePath,
109
- projectConfigDir,
110
109
  daemonPidPath,
111
110
  orchestratorLogPath,
112
111
  httpStatusPath,
@@ -25,14 +25,6 @@ var GitHubScopeError = class extends GitHubApiError {
25
25
  this.name = "GitHubScopeError";
26
26
  }
27
27
  };
28
- var GitHubRepositoryLookupError = class extends GitHubApiError {
29
- constructor(reason, message, remediation, status) {
30
- super(message, status);
31
- this.reason = reason;
32
- this.remediation = remediation;
33
- this.name = "GitHubRepositoryLookupError";
34
- }
35
- };
36
28
  function createClient(token, options) {
37
29
  return {
38
30
  token,
@@ -40,77 +32,6 @@ function createClient(token, options) {
40
32
  fetchImpl: options?.fetchImpl ?? fetch
41
33
  };
42
34
  }
43
- async function getRepositoryMetadata(client, owner, name) {
44
- const restUrl = client.apiUrl.replace("/graphql", "");
45
- const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
46
- const repoPath = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`;
47
- let response;
48
- try {
49
- response = await client.fetchImpl(`${baseUrl}${repoPath}`, {
50
- headers: {
51
- authorization: `Bearer ${client.token}`,
52
- accept: "application/vnd.github+json"
53
- }
54
- });
55
- } catch (error) {
56
- const detail = error instanceof Error && error.message.length > 0 ? ` ${error.message}` : "";
57
- throw new GitHubRepositoryLookupError(
58
- "offline",
59
- `GitHub repository validation could not reach the API.${detail}`.trim(),
60
- "Check your network connection and re-run the command to validate before saving."
61
- );
62
- }
63
- if (!response.ok) {
64
- const payload = await response.json().catch(() => null);
65
- const message = payload?.message?.trim() || response.statusText;
66
- if (response.status === 403 && (response.headers.get("x-ratelimit-remaining") === "0" || /rate limit/i.test(message))) {
67
- throw new GitHubRepositoryLookupError(
68
- "rate_limited",
69
- "GitHub API rate limit blocked repository validation.",
70
- "Wait for the rate limit window to reset, then re-run 'gh-symphony repo add owner/name'.",
71
- response.status
72
- );
73
- }
74
- if (response.status === 401) {
75
- throw new GitHubRepositoryLookupError(
76
- "invalid_token",
77
- "GitHub token is invalid or expired.",
78
- "Run 'gh auth login --scopes repo,read:org,project' or refresh GITHUB_GRAPHQL_TOKEN, then retry.",
79
- response.status
80
- );
81
- }
82
- if (response.status === 403 || /resource not accessible|saml|single sign-on|access denied/i.test(message)) {
83
- throw new GitHubRepositoryLookupError(
84
- "no_access",
85
- `GitHub denied access to ${owner}/${name}.`,
86
- "Confirm that the authenticated user can read this repository and that the token has the required access.",
87
- response.status
88
- );
89
- }
90
- if (response.status === 404) {
91
- throw new GitHubRepositoryLookupError(
92
- "not_found",
93
- `Repository ${owner}/${name} was not found.`,
94
- "Check the owner/name spelling. If the repository is private, confirm the current token can access it.",
95
- response.status
96
- );
97
- }
98
- throw new GitHubRepositoryLookupError(
99
- "unknown",
100
- `GitHub repository validation failed: ${response.status} ${message}`.trim(),
101
- "Retry the command. If the problem continues, verify GitHub API access separately.",
102
- response.status
103
- );
104
- }
105
- const repo = await response.json();
106
- return {
107
- owner: repo.owner.login,
108
- name: repo.name,
109
- url: repo.html_url,
110
- cloneUrl: repo.clone_url,
111
- visibility: repo.visibility ?? (repo.private === true ? "private" : "public")
112
- };
113
- }
114
35
  async function validateToken(client) {
115
36
  const restUrl = client.apiUrl.replace("/graphql", "");
116
37
  const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
@@ -817,9 +738,7 @@ export {
817
738
  findLinkedRepository,
818
739
  GitHubApiError,
819
740
  GitHubScopeError,
820
- GitHubRepositoryLookupError,
821
741
  createClient,
822
- getRepositoryMetadata,
823
742
  validateToken,
824
743
  checkRequiredScopes,
825
744
  discoverUserProjects,
@@ -3,7 +3,7 @@ import {
3
3
  configFilePath,
4
4
  loadGlobalConfig,
5
5
  saveGlobalConfig
6
- } from "./chunk-QIRE2VXS.js";
6
+ } from "./chunk-WOVNN5NW.js";
7
7
 
8
8
  // src/commands/config-cmd.ts
9
9
  import { spawn } from "child_process";