@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.
- package/README.md +72 -77
- package/dist/{chunk-HMLBBZNY.js → chunk-2YF7PQUC.js} +16 -71
- package/dist/{chunk-IWFX2FMA.js → chunk-6I753NYO.js} +4 -1
- package/dist/{chunk-2TSM3INR.js → chunk-HQ7A3C7K.js} +575 -12
- package/dist/{chunk-36KYEDEO.js → chunk-MVRF7BES.js} +1 -10
- package/dist/{workflow-L3KT6HB7.js → chunk-NESHTYXQ.js} +27 -19
- package/dist/{chunk-2UW7NQLX.js → chunk-PEZUBHWJ.js} +1 -1
- package/dist/chunk-PG332ZS4.js +238 -0
- package/dist/{chunk-EEQQWTXS.js → chunk-WCOIVNHH.js} +213 -82
- package/dist/{chunk-QIRE2VXS.js → chunk-WOVNN5NW.js} +16 -17
- package/dist/{chunk-C67H3OUL.js → chunk-Z3NZOPLZ.js} +0 -81
- package/dist/{config-cmd-Z3A7V6NC.js → config-cmd-2ADPUYWA.js} +1 -1
- package/dist/{doctor-EJUMPBMW.js → doctor-2AXHIEAP.js} +464 -40
- package/dist/index.js +340 -294
- package/dist/{chunk-PUDXVBSN.js → repo-SUXYT4OK.js} +6272 -2996
- package/dist/{setup-TZJSM3QV.js → setup-UBHOMXUG.js} +57 -92
- package/dist/{upgrade-O33S2SJK.js → upgrade-355SQJ5P.js} +2 -2
- package/dist/{version-CW54Q7BK.js → version-4ILSDZQH.js} +1 -1
- package/dist/worker-entry.js +10 -5
- package/dist/workflow-S6YSZPQT.js +22 -0
- package/package.json +4 -4
- package/dist/chunk-DDL4BWSL.js +0 -146
- package/dist/chunk-DFLXHNYQ.js +0 -482
- package/dist/chunk-E7HYEEZD.js +0 -1318
- package/dist/chunk-GDE6FYN4.js +0 -26
- package/dist/chunk-GSX2FV3M.js +0 -103
- package/dist/chunk-ZHOKYUO3.js +0 -1047
- package/dist/init-54HMKNYI.js +0 -38
- package/dist/logs-GTZ4U5JE.js +0 -188
- package/dist/project-RMYMZSFV.js +0 -25
- package/dist/recover-LTLKMTRX.js +0 -133
- package/dist/repo-WI7GF6XQ.js +0 -749
- package/dist/run-IHN3ZL35.js +0 -122
- package/dist/start-RTAHQMR2.js +0 -19
- package/dist/status-F4D52OVK.js +0 -12
- 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(
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2274
|
+
function resolveRuntimeMcpConfigPath(workspaceRoot, env) {
|
|
2149
2275
|
const normalizedWorkspaceRoot = resolve4(workspaceRoot);
|
|
2150
|
-
const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join3(
|
|
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
|
-
|
|
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
|
-
|
|
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,
|