@gh-symphony/cli 0.0.22 → 0.1.2
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-IWFX2FMA.js → chunk-6I753NYO.js} +4 -1
- package/dist/{workflow-L3KT6HB7.js → chunk-B4ZJMAZL.js} +27 -19
- package/dist/{chunk-2TSM3INR.js → chunk-DLZAJXZL.js} +575 -12
- package/dist/chunk-GHVDABFO.js +235 -0
- package/dist/{chunk-EEQQWTXS.js → chunk-GPRCOJDJ.js} +158 -75
- package/dist/{chunk-36KYEDEO.js → chunk-MVRF7BES.js} +1 -10
- package/dist/{chunk-2UW7NQLX.js → chunk-VFHMHHZW.js} +1 -1
- package/dist/{chunk-HMLBBZNY.js → chunk-WM2B6BJ7.js} +16 -71
- 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-EEPNFCGF.js} +464 -40
- package/dist/index.js +340 -294
- package/dist/{chunk-PUDXVBSN.js → repo-RX4OK7XH.js} +5944 -3001
- package/dist/{setup-TZJSM3QV.js → setup-XNHHRBGU.js} +57 -92
- package/dist/{upgrade-O33S2SJK.js → upgrade-NS53EO2B.js} +2 -2
- package/dist/{version-CW54Q7BK.js → version-2RHFZ5CI.js} +1 -1
- package/dist/worker-entry.js +10 -5
- package/dist/workflow-26QNZZWH.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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveRepoRuntimeRoot
|
|
4
|
+
} from "./chunk-6I753NYO.js";
|
|
5
|
+
import {
|
|
6
|
+
parseWorkflowMarkdown
|
|
7
|
+
} from "./chunk-GPRCOJDJ.js";
|
|
8
|
+
import {
|
|
9
|
+
saveGlobalConfig,
|
|
10
|
+
saveProjectConfig
|
|
11
|
+
} from "./chunk-WOVNN5NW.js";
|
|
12
|
+
|
|
13
|
+
// src/repo-runtime.ts
|
|
14
|
+
import { execFileSync } from "child_process";
|
|
15
|
+
import {
|
|
16
|
+
mkdir,
|
|
17
|
+
readdir,
|
|
18
|
+
readFile,
|
|
19
|
+
rename,
|
|
20
|
+
rm,
|
|
21
|
+
stat,
|
|
22
|
+
writeFile
|
|
23
|
+
} from "fs/promises";
|
|
24
|
+
import { basename, dirname, join, resolve } from "path";
|
|
25
|
+
var INTERNAL_PROJECT_ID = "repository";
|
|
26
|
+
var RepoRuntimeMigrationError = class extends Error {
|
|
27
|
+
};
|
|
28
|
+
function parseRepoRuntimeFlags(args) {
|
|
29
|
+
const flags = { repoDir: process.cwd() };
|
|
30
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
31
|
+
const arg = args[i];
|
|
32
|
+
const value = args[i + 1];
|
|
33
|
+
if (arg === "--repo-dir") {
|
|
34
|
+
if (!value || value.startsWith("-")) {
|
|
35
|
+
throw new Error("Option '--repo-dir' argument missing");
|
|
36
|
+
}
|
|
37
|
+
flags.repoDir = resolve(value);
|
|
38
|
+
i += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (arg === "--workflow-file") {
|
|
42
|
+
if (!value || value.startsWith("-")) {
|
|
43
|
+
throw new Error("Option '--workflow-file' argument missing");
|
|
44
|
+
}
|
|
45
|
+
flags.workflowFile = value;
|
|
46
|
+
i += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg?.startsWith("-")) {
|
|
50
|
+
throw new Error(`Unknown option '${arg}'`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return flags;
|
|
54
|
+
}
|
|
55
|
+
async function initRepoRuntime(flags) {
|
|
56
|
+
const repoDir = resolve(flags.repoDir);
|
|
57
|
+
const runtimeRoot = resolveRepoRuntimeRoot(repoDir);
|
|
58
|
+
await migrateLegacyRuntime(runtimeRoot);
|
|
59
|
+
const workflowPath = resolve(repoDir, flags.workflowFile ?? "WORKFLOW.md");
|
|
60
|
+
const workflow = parseWorkflowMarkdown(await readFile(workflowPath, "utf8"));
|
|
61
|
+
const repository = resolveRepository(repoDir);
|
|
62
|
+
const trackerAdapter = workflow.tracker.kind ?? "github-project";
|
|
63
|
+
const trackerBindingId = workflow.tracker.projectId ?? workflow.tracker.projectSlug ?? "";
|
|
64
|
+
const trackerSettings = {
|
|
65
|
+
...workflow.tracker.projectId ? { projectId: workflow.tracker.projectId } : {},
|
|
66
|
+
repository: `${repository.owner}/${repository.name}`
|
|
67
|
+
};
|
|
68
|
+
if (flags.assignedOnly) {
|
|
69
|
+
trackerSettings.assignedOnly = true;
|
|
70
|
+
}
|
|
71
|
+
if (trackerAdapter === "file") {
|
|
72
|
+
if (!process.env.GH_SYMPHONY_FILE_TRACKER_ISSUES_PATH) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"File tracker repo init requires GH_SYMPHONY_FILE_TRACKER_ISSUES_PATH to point to the issues fixture."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
trackerSettings.issuesPath = process.env.GH_SYMPHONY_FILE_TRACKER_ISSUES_PATH;
|
|
78
|
+
}
|
|
79
|
+
const projectConfig = {
|
|
80
|
+
projectId: INTERNAL_PROJECT_ID,
|
|
81
|
+
slug: basename(repoDir) || INTERNAL_PROJECT_ID,
|
|
82
|
+
displayName: `${repository.owner}/${repository.name}`,
|
|
83
|
+
workspaceDir: repoDir,
|
|
84
|
+
repository,
|
|
85
|
+
tracker: {
|
|
86
|
+
adapter: trackerAdapter,
|
|
87
|
+
bindingId: trackerBindingId,
|
|
88
|
+
settings: trackerSettings
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
92
|
+
await saveProjectConfig(runtimeRoot, INTERNAL_PROJECT_ID, projectConfig);
|
|
93
|
+
await saveGlobalConfig(runtimeRoot, {
|
|
94
|
+
activeProject: INTERNAL_PROJECT_ID,
|
|
95
|
+
projects: [INTERNAL_PROJECT_ID]
|
|
96
|
+
});
|
|
97
|
+
const orchestratorConfig = {
|
|
98
|
+
projectId: INTERNAL_PROJECT_ID,
|
|
99
|
+
slug: projectConfig.slug,
|
|
100
|
+
workspaceDir: repoDir,
|
|
101
|
+
repository,
|
|
102
|
+
tracker: projectConfig.tracker
|
|
103
|
+
};
|
|
104
|
+
await writeJsonFile(join(runtimeRoot, "project.json"), orchestratorConfig);
|
|
105
|
+
return {
|
|
106
|
+
configDir: runtimeRoot,
|
|
107
|
+
projectId: INTERNAL_PROJECT_ID,
|
|
108
|
+
workflowPath,
|
|
109
|
+
repository
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function migrateLegacyRuntime(runtimeRoot) {
|
|
113
|
+
const projectsDir = join(runtimeRoot, "projects");
|
|
114
|
+
const projectIds = await readDirectoryNames(projectsDir);
|
|
115
|
+
if (projectIds.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (projectIds.length === 1 && projectIds[0] === INTERNAL_PROJECT_ID && await pathExists(join(runtimeRoot, "project.json"))) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (projectIds.length > 1) {
|
|
122
|
+
throw new RepoRuntimeMigrationError(
|
|
123
|
+
[
|
|
124
|
+
"Multiple legacy project runtime directories were found under .runtime/orchestrator/projects.",
|
|
125
|
+
`Found: ${projectIds.join(", ")}`,
|
|
126
|
+
"Automatic migration is only supported when exactly one project directory exists.",
|
|
127
|
+
"Manually keep the project directory you want to promote, archive or remove the others, then re-run 'gh-symphony repo init'."
|
|
128
|
+
].join("\n")
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const sourceDir = join(projectsDir, projectIds[0]);
|
|
132
|
+
const entries = await readdir(sourceDir);
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const target = join(runtimeRoot, entry);
|
|
135
|
+
if (await pathExists(target)) {
|
|
136
|
+
throw new RepoRuntimeMigrationError(
|
|
137
|
+
`Cannot promote legacy runtime data because '${entry}' already exists in .runtime/orchestrator. Move or remove it, then re-run 'gh-symphony repo init'.`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
await rename(join(sourceDir, entry), target);
|
|
141
|
+
}
|
|
142
|
+
await stripProjectIdFromRunRecords(join(runtimeRoot, "runs"));
|
|
143
|
+
await rm(projectsDir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
async function stripProjectIdFromRunRecords(runsDir) {
|
|
146
|
+
for (const runId of await readDirectoryNames(runsDir)) {
|
|
147
|
+
const runPath = join(runsDir, runId, "run.json");
|
|
148
|
+
const run = await readJsonFile(runPath);
|
|
149
|
+
if (!run || !("projectId" in run)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
delete run.projectId;
|
|
153
|
+
await writeJsonFile(runPath, run);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function readDirectoryNames(path) {
|
|
157
|
+
try {
|
|
158
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
159
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (isMissing(error)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function resolveRepository(repoDir) {
|
|
168
|
+
const remote = readGitOrigin(repoDir);
|
|
169
|
+
const cleanedRemote = remote.replace(/\.git$/, "");
|
|
170
|
+
const match = cleanedRemote.match(/github\.com[:/]([^/]+)\/([^/]+)$/) ?? cleanedRemote.match(/^([^/]+)\/([^/]+)$/);
|
|
171
|
+
if (!match) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Unable to infer GitHub repository from git remote 'origin'. Run from a cloned GitHub repository or set origin to owner/name."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
owner: match[1],
|
|
178
|
+
name: match[2],
|
|
179
|
+
cloneUrl: remote.startsWith("http") ? remote : `https://github.com/${match[1]}/${match[2]}.git`,
|
|
180
|
+
path: repoDir
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function readGitOrigin(repoDir) {
|
|
184
|
+
try {
|
|
185
|
+
return execFileSync(
|
|
186
|
+
"git",
|
|
187
|
+
["-C", repoDir, "config", "--get", "remote.origin.url"],
|
|
188
|
+
{
|
|
189
|
+
encoding: "utf8",
|
|
190
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
191
|
+
}
|
|
192
|
+
).trim();
|
|
193
|
+
} catch {
|
|
194
|
+
throw new Error(
|
|
195
|
+
"Unable to read git remote 'origin'. Run 'gh-symphony repo init' inside a cloned repository."
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function readJsonFile(path) {
|
|
200
|
+
try {
|
|
201
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (isMissing(error)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function writeJsonFile(path, value) {
|
|
210
|
+
await mkdir(dirname(path), { recursive: true });
|
|
211
|
+
const temporaryPath = `${path}.tmp`;
|
|
212
|
+
await writeFile(temporaryPath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
213
|
+
await rename(temporaryPath, path);
|
|
214
|
+
}
|
|
215
|
+
async function pathExists(path) {
|
|
216
|
+
try {
|
|
217
|
+
await stat(path);
|
|
218
|
+
return true;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (isMissing(error)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function isMissing(error) {
|
|
227
|
+
return Boolean(
|
|
228
|
+
error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export {
|
|
233
|
+
parseRepoRuntimeFlags,
|
|
234
|
+
initRepoRuntime
|
|
235
|
+
};
|
|
@@ -583,73 +583,6 @@ function matchOptionalSection(markdown, heading) {
|
|
|
583
583
|
return match?.[1]?.trim() ?? null;
|
|
584
584
|
}
|
|
585
585
|
|
|
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
586
|
// ../core/src/workflow/render.ts
|
|
654
587
|
import {
|
|
655
588
|
Liquid,
|
|
@@ -659,6 +592,17 @@ import {
|
|
|
659
592
|
UndefinedVariableError
|
|
660
593
|
} from "liquidjs";
|
|
661
594
|
function buildPromptVariables(issue, options) {
|
|
595
|
+
const contentType = issue.metadata.contentType ?? "Issue";
|
|
596
|
+
const linkedPullRequests = Array.isArray(issue.metadata.linkedPullRequests) ? issue.metadata.linkedPullRequests.map(normalizePullRequestContext) : [];
|
|
597
|
+
const primaryPullRequest = contentType === "PullRequest" ? normalizePullRequestContext(
|
|
598
|
+
issue.metadata.pullRequest ?? linkedPullRequests[0] ?? buildPullRequestContextFromIssue(issue)
|
|
599
|
+
) : linkedPullRequests[0] ?? null;
|
|
600
|
+
const pullRequestContext = buildPullRequestVariables({
|
|
601
|
+
contentType,
|
|
602
|
+
linkedPullRequests,
|
|
603
|
+
primaryPullRequest,
|
|
604
|
+
issueBranchName: issue.branchName
|
|
605
|
+
});
|
|
662
606
|
return {
|
|
663
607
|
issue: {
|
|
664
608
|
id: issue.id,
|
|
@@ -672,13 +616,61 @@ function buildPromptVariables(issue, options) {
|
|
|
672
616
|
labels: issue.labels,
|
|
673
617
|
blocked_by: issue.blockedBy,
|
|
674
618
|
branch_name: issue.branchName,
|
|
619
|
+
content_type: contentType,
|
|
620
|
+
linked_pull_requests: linkedPullRequests,
|
|
621
|
+
primary_pull_request: primaryPullRequest,
|
|
622
|
+
has_linked_pr: linkedPullRequests.length > 0,
|
|
675
623
|
created_at: issue.createdAt,
|
|
676
624
|
updated_at: issue.updatedAt,
|
|
677
625
|
repository: `${issue.repository.owner}/${issue.repository.name}`
|
|
678
626
|
},
|
|
627
|
+
pull_request_context: pullRequestContext,
|
|
679
628
|
attempt: options.attempt
|
|
680
629
|
};
|
|
681
630
|
}
|
|
631
|
+
function normalizePullRequestContext(pullRequest) {
|
|
632
|
+
return {
|
|
633
|
+
...pullRequest,
|
|
634
|
+
state: pullRequest.state ?? null,
|
|
635
|
+
projectState: pullRequest.projectState ?? null,
|
|
636
|
+
isDraft: pullRequest.isDraft ?? null,
|
|
637
|
+
merged: pullRequest.merged ?? null,
|
|
638
|
+
headRefName: pullRequest.headRefName ?? null,
|
|
639
|
+
baseRefName: pullRequest.baseRefName ?? null
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function buildPullRequestVariables(options) {
|
|
643
|
+
const checkoutBranch = options.primaryPullRequest?.headRefName ?? (options.contentType === "PullRequest" ? options.issueBranchName : null);
|
|
644
|
+
const hasPrimaryPr = options.primaryPullRequest !== null;
|
|
645
|
+
return {
|
|
646
|
+
subject_type: options.contentType,
|
|
647
|
+
linked_pull_requests: options.linkedPullRequests,
|
|
648
|
+
primary_pull_request: options.primaryPullRequest,
|
|
649
|
+
has_linked_pr: options.linkedPullRequests.length > 0,
|
|
650
|
+
has_primary_pr: hasPrimaryPr,
|
|
651
|
+
checkout_branch: checkoutBranch,
|
|
652
|
+
// Policy flag for prompt templates: any primary PR means the worker must
|
|
653
|
+
// inspect review threads and checks before editing code.
|
|
654
|
+
review_first: hasPrimaryPr
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function buildPullRequestContextFromIssue(issue) {
|
|
658
|
+
return {
|
|
659
|
+
id: issue.id,
|
|
660
|
+
number: issue.number,
|
|
661
|
+
identifier: issue.identifier,
|
|
662
|
+
url: issue.url,
|
|
663
|
+
state: null,
|
|
664
|
+
projectState: issue.state,
|
|
665
|
+
headRefName: issue.branchName,
|
|
666
|
+
repository: {
|
|
667
|
+
owner: issue.repository.owner,
|
|
668
|
+
name: issue.repository.name,
|
|
669
|
+
url: issue.repository.url ?? "",
|
|
670
|
+
cloneUrl: issue.repository.cloneUrl
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
682
674
|
var STRICT_LIQUID_ENGINE = new Liquid({
|
|
683
675
|
strictVariables: true,
|
|
684
676
|
strictFilters: true,
|
|
@@ -873,6 +865,73 @@ function isOrchestratorChannelEvent(value) {
|
|
|
873
865
|
return false;
|
|
874
866
|
}
|
|
875
867
|
|
|
868
|
+
// ../core/src/workflow/loader.ts
|
|
869
|
+
import { createHash } from "crypto";
|
|
870
|
+
import { access, readFile, stat } from "fs/promises";
|
|
871
|
+
import { constants } from "fs";
|
|
872
|
+
var WorkflowConfigStore = class {
|
|
873
|
+
cache = /* @__PURE__ */ new Map();
|
|
874
|
+
async load(workflowPath, env = process.env) {
|
|
875
|
+
await access(workflowPath, constants.R_OK);
|
|
876
|
+
const fileStat = await stat(workflowPath);
|
|
877
|
+
const cached = this.cache.get(workflowPath);
|
|
878
|
+
const markdown = await readFile(workflowPath, "utf8");
|
|
879
|
+
const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
|
|
880
|
+
if (cached && cached.fingerprint === fingerprint) {
|
|
881
|
+
return toWorkflowResolution(workflowPath, cached.workflow, {
|
|
882
|
+
isValid: true,
|
|
883
|
+
usedLastKnownGood: false,
|
|
884
|
+
validationError: null
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
try {
|
|
888
|
+
const workflow = parseWorkflowMarkdown(markdown, env);
|
|
889
|
+
this.cache.set(workflowPath, {
|
|
890
|
+
fingerprint,
|
|
891
|
+
workflow,
|
|
892
|
+
loadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
893
|
+
});
|
|
894
|
+
return toWorkflowResolution(workflowPath, workflow, {
|
|
895
|
+
isValid: true,
|
|
896
|
+
usedLastKnownGood: false,
|
|
897
|
+
validationError: null
|
|
898
|
+
});
|
|
899
|
+
} catch (error) {
|
|
900
|
+
if (cached) {
|
|
901
|
+
return toWorkflowResolution(workflowPath, cached.workflow, {
|
|
902
|
+
isValid: false,
|
|
903
|
+
usedLastKnownGood: true,
|
|
904
|
+
validationError: error instanceof Error ? error.message : "Invalid workflow definition."
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
throw error;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
function createDefaultWorkflowResolution() {
|
|
912
|
+
return createInvalidWorkflowResolution(null, "missing_workflow_file");
|
|
913
|
+
}
|
|
914
|
+
function createInvalidWorkflowResolution(workflowPath, validationError) {
|
|
915
|
+
return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
|
|
916
|
+
isValid: false,
|
|
917
|
+
usedLastKnownGood: false,
|
|
918
|
+
validationError
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
function toWorkflowResolution(workflowPath, workflow, metadata) {
|
|
922
|
+
return {
|
|
923
|
+
workflowPath,
|
|
924
|
+
workflow,
|
|
925
|
+
lifecycle: workflow.lifecycle,
|
|
926
|
+
promptTemplate: workflow.promptTemplate,
|
|
927
|
+
agentCommand: workflow.agentCommand,
|
|
928
|
+
hookPath: workflow.hookPath ?? "",
|
|
929
|
+
isValid: metadata.isValid,
|
|
930
|
+
usedLastKnownGood: metadata.usedLastKnownGood,
|
|
931
|
+
validationError: metadata.validationError
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
876
935
|
// ../core/src/workflow/exit-classification.ts
|
|
877
936
|
function classifySessionExit(params) {
|
|
878
937
|
if (params.userInputRequired) {
|
|
@@ -1471,7 +1530,7 @@ async function runClaudePreflight(options, dependencies = {}) {
|
|
|
1471
1530
|
const command = resolveRuntimeCommandBinary(options.command) ?? "claude";
|
|
1472
1531
|
const checks = [];
|
|
1473
1532
|
checks.push(checkClaudeBinary(command, options.cwd, deps));
|
|
1474
|
-
checks.push(await
|
|
1533
|
+
checks.push(await checkClaudeAuthentication(env, options, deps));
|
|
1475
1534
|
checks.push(await checkWorkspaceMcpConfig(options.cwd, deps));
|
|
1476
1535
|
if (options.includeGhAuth) {
|
|
1477
1536
|
checks.push(checkGhAuthentication(options.cwd, deps));
|
|
@@ -1573,11 +1632,12 @@ function checkGhAuthentication(cwd, deps) {
|
|
|
1573
1632
|
);
|
|
1574
1633
|
}
|
|
1575
1634
|
}
|
|
1576
|
-
async function
|
|
1635
|
+
async function checkClaudeAuthentication(env, options, deps) {
|
|
1636
|
+
const authMode = options.authMode ?? "api-key-required";
|
|
1577
1637
|
if (env.ANTHROPIC_API_KEY?.trim()) {
|
|
1578
1638
|
return pass(
|
|
1579
1639
|
"anthropic_api_key",
|
|
1580
|
-
"
|
|
1640
|
+
"Claude authentication",
|
|
1581
1641
|
"ANTHROPIC_API_KEY is configured in the environment.",
|
|
1582
1642
|
{ source: "env" }
|
|
1583
1643
|
);
|
|
@@ -1585,9 +1645,32 @@ async function checkAnthropicApiKey(env, options, deps) {
|
|
|
1585
1645
|
const brokerUrl = env.AGENT_CREDENTIAL_BROKER_URL?.trim();
|
|
1586
1646
|
const brokerSecret = env.AGENT_CREDENTIAL_BROKER_SECRET?.trim();
|
|
1587
1647
|
if (!brokerUrl || !brokerSecret) {
|
|
1648
|
+
if (authMode === "local-or-api-key" && !brokerUrl && !brokerSecret) {
|
|
1649
|
+
return warn(
|
|
1650
|
+
"anthropic_api_key",
|
|
1651
|
+
"Claude authentication",
|
|
1652
|
+
"ANTHROPIC_API_KEY and agent credential broker are not configured; Claude Code local login may be used for this non-bare runtime.",
|
|
1653
|
+
"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.",
|
|
1654
|
+
{ source: "local" }
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
if (brokerUrl || brokerSecret) {
|
|
1658
|
+
const missingName = brokerUrl ? "AGENT_CREDENTIAL_BROKER_SECRET" : "AGENT_CREDENTIAL_BROKER_URL";
|
|
1659
|
+
return fail(
|
|
1660
|
+
"anthropic_api_key",
|
|
1661
|
+
"Claude authentication",
|
|
1662
|
+
`Agent credential broker configuration is incomplete: ${missingName} is not configured.`,
|
|
1663
|
+
`Set ${missingName} or remove the partial broker configuration and use ANTHROPIC_API_KEY instead.`,
|
|
1664
|
+
{
|
|
1665
|
+
source: "broker",
|
|
1666
|
+
brokerUrl: brokerUrl ?? null,
|
|
1667
|
+
missing: missingName
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1588
1671
|
return fail(
|
|
1589
1672
|
"anthropic_api_key",
|
|
1590
|
-
"
|
|
1673
|
+
"Claude authentication",
|
|
1591
1674
|
"Neither ANTHROPIC_API_KEY nor an agent credential broker is configured.",
|
|
1592
1675
|
"Set ANTHROPIC_API_KEY or configure AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
|
|
1593
1676
|
{ source: "missing" }
|
|
@@ -1596,7 +1679,7 @@ async function checkAnthropicApiKey(env, options, deps) {
|
|
|
1596
1679
|
if (options.probeCredentialBroker === false) {
|
|
1597
1680
|
return pass(
|
|
1598
1681
|
"anthropic_api_key",
|
|
1599
|
-
"
|
|
1682
|
+
"Claude authentication",
|
|
1600
1683
|
"Agent credential broker configuration is present.",
|
|
1601
1684
|
{ source: "broker", brokerUrl }
|
|
1602
1685
|
);
|
|
@@ -1614,14 +1697,14 @@ async function checkAnthropicApiKey(env, options, deps) {
|
|
|
1614
1697
|
if (response.ok && payload.env?.ANTHROPIC_API_KEY?.trim()) {
|
|
1615
1698
|
return pass(
|
|
1616
1699
|
"anthropic_api_key",
|
|
1617
|
-
"
|
|
1700
|
+
"Claude authentication",
|
|
1618
1701
|
"Agent credential broker is reachable and returned ANTHROPIC_API_KEY.",
|
|
1619
1702
|
{ source: "broker", brokerUrl }
|
|
1620
1703
|
);
|
|
1621
1704
|
}
|
|
1622
1705
|
return fail(
|
|
1623
1706
|
"anthropic_api_key",
|
|
1624
|
-
"
|
|
1707
|
+
"Claude authentication",
|
|
1625
1708
|
payload.error ? `Agent credential broker did not return ANTHROPIC_API_KEY: ${payload.error}.` : "Agent credential broker did not return ANTHROPIC_API_KEY.",
|
|
1626
1709
|
"Set ANTHROPIC_API_KEY or configure the credential broker to return ANTHROPIC_API_KEY.",
|
|
1627
1710
|
{ source: "broker", brokerUrl, status: response.status }
|
|
@@ -1629,7 +1712,7 @@ async function checkAnthropicApiKey(env, options, deps) {
|
|
|
1629
1712
|
} catch (error) {
|
|
1630
1713
|
return fail(
|
|
1631
1714
|
"anthropic_api_key",
|
|
1632
|
-
"
|
|
1715
|
+
"Claude authentication",
|
|
1633
1716
|
"Agent credential broker could not be reached.",
|
|
1634
1717
|
"Set ANTHROPIC_API_KEY or fix AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
|
|
1635
1718
|
{
|
|
@@ -51,14 +51,6 @@ 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
|
-
|
|
62
54
|
export {
|
|
63
55
|
bold,
|
|
64
56
|
dim,
|
|
@@ -72,6 +64,5 @@ export {
|
|
|
72
64
|
setNoColor,
|
|
73
65
|
clearScreen,
|
|
74
66
|
hideCursor,
|
|
75
|
-
showCursor
|
|
76
|
-
formatRepositoryDisplay
|
|
67
|
+
showCursor
|
|
77
68
|
};
|