@gh-symphony/cli 0.0.18 → 0.0.19
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 +85 -14
- package/dist/{chunk-LZE6YUSB.js → chunk-6CI3UUMH.js} +16 -5
- package/dist/{chunk-ZYYY55WB.js → chunk-GKENCODJ.js} +74 -23
- package/dist/{project-O57C32WF.js → chunk-H2YXSYOZ.js} +104 -90
- package/dist/{chunk-5YLETHMR.js → chunk-RN2PACNV.js} +345 -169
- package/dist/{chunk-62L6QQE6.js → chunk-TILHWBP6.js} +277 -1
- package/dist/{config-cmd-AZ7POMAA.js → config-cmd-DNXNL26Z.js} +3 -1
- package/dist/doctor-IYHCFXOZ.js +1126 -0
- package/dist/index.js +144 -18
- package/dist/init-KZT6YNOH.js +33 -0
- package/dist/project-DNALEWO3.js +22 -0
- package/dist/{recover-UGUTQTWA.js → recover-C3V2QAUB.js} +2 -2
- package/dist/repo-HDDE7OUI.js +321 -0
- package/dist/{run-5H2R6CHB.js → run-XI2S5Y4V.js} +2 -2
- package/dist/setup-K4CYYJBF.js +431 -0
- package/dist/{start-5JGGJIMC.js → start-M6IQGRFO.js} +4 -4
- package/dist/upgrade-F4VE4XBS.js +165 -0
- package/dist/{version-N7YXKG6V.js → version-Y5RYNWMF.js} +1 -1
- package/dist/worker-entry.js +24 -2
- package/dist/workflow-TBIFY5MO.js +497 -0
- package/package.json +4 -4
- package/dist/chunk-7UBUBSMH.js +0 -134
- package/dist/doctor-3QT5CZN4.js +0 -532
- package/dist/init-E432UZ32.js +0 -18
- package/dist/repo-R3XBIVAX.js +0 -121
- package/dist/{chunk-OL73UN2X.js → chunk-M3IFVLQS.js} +77 -77
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
init_default
|
|
4
|
+
} from "./chunk-RN2PACNV.js";
|
|
5
|
+
import {
|
|
6
|
+
buildPromptVariables,
|
|
7
|
+
parseWorkflowMarkdown,
|
|
8
|
+
renderPrompt
|
|
9
|
+
} from "./chunk-M3IFVLQS.js";
|
|
10
|
+
import "./chunk-TILHWBP6.js";
|
|
11
|
+
import "./chunk-ROGRTUFI.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/workflow.ts
|
|
14
|
+
import { readFile } from "fs/promises";
|
|
15
|
+
import { resolve } from "path";
|
|
16
|
+
var SAMPLE_ISSUE = {
|
|
17
|
+
id: "issue-157-sample",
|
|
18
|
+
identifier: "octo/hello-world#157",
|
|
19
|
+
number: 157,
|
|
20
|
+
title: "Add workflow validate and preview commands",
|
|
21
|
+
description: "Expose strict WORKFLOW.md validation and prompt preview flows in the CLI.",
|
|
22
|
+
priority: 1,
|
|
23
|
+
state: "In progress",
|
|
24
|
+
branchName: "feat/workflow-cli-preview",
|
|
25
|
+
url: "https://github.com/octo/hello-world/issues/157",
|
|
26
|
+
labels: ["enhancement", "cli"],
|
|
27
|
+
blockedBy: [
|
|
28
|
+
{
|
|
29
|
+
id: "issue-120",
|
|
30
|
+
identifier: "octo/hello-world#120",
|
|
31
|
+
state: "Done"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
createdAt: "2026-03-31T02:06:39Z",
|
|
35
|
+
updatedAt: "2026-04-03T02:28:21Z",
|
|
36
|
+
repository: {
|
|
37
|
+
owner: "octo",
|
|
38
|
+
name: "hello-world",
|
|
39
|
+
cloneUrl: "https://github.com/octo/hello-world.git",
|
|
40
|
+
url: "https://github.com/octo/hello-world"
|
|
41
|
+
},
|
|
42
|
+
tracker: {
|
|
43
|
+
adapter: "github-project",
|
|
44
|
+
bindingId: "sample-binding",
|
|
45
|
+
itemId: "sample-item"
|
|
46
|
+
},
|
|
47
|
+
metadata: {}
|
|
48
|
+
};
|
|
49
|
+
var SAMPLE_CONTINUATION_VARIABLES = {
|
|
50
|
+
lastTurnSummary: "Validated the prompt template and updated the CLI routing.",
|
|
51
|
+
cumulativeTurnCount: 3
|
|
52
|
+
};
|
|
53
|
+
function parseWorkflowArgs(args) {
|
|
54
|
+
const [subcommand, ...rest] = args;
|
|
55
|
+
if (!subcommand) {
|
|
56
|
+
return { args: [] };
|
|
57
|
+
}
|
|
58
|
+
if (subcommand === "init" || subcommand === "validate" || subcommand === "preview") {
|
|
59
|
+
return { subcommand, args: rest };
|
|
60
|
+
}
|
|
61
|
+
if (subcommand === "--help" || subcommand === "-h") {
|
|
62
|
+
return { args: ["--help"] };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
args: rest,
|
|
66
|
+
error: `Unknown workflow subcommand '${subcommand}'`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function parseValidateFlags(args) {
|
|
70
|
+
const flags = {};
|
|
71
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
72
|
+
const arg = args[i];
|
|
73
|
+
if (arg === "--file") {
|
|
74
|
+
const value = args[i + 1];
|
|
75
|
+
if (!value || value.startsWith("-")) {
|
|
76
|
+
throw new Error("Option '--file' argument missing");
|
|
77
|
+
}
|
|
78
|
+
flags.file = value;
|
|
79
|
+
i += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg?.startsWith("-")) {
|
|
83
|
+
throw new Error(`Unknown option '${arg}'`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return flags;
|
|
87
|
+
}
|
|
88
|
+
function parsePreviewFlags(args) {
|
|
89
|
+
const flags = {
|
|
90
|
+
attempt: null
|
|
91
|
+
};
|
|
92
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
93
|
+
const arg = args[i];
|
|
94
|
+
const value = args[i + 1];
|
|
95
|
+
switch (arg) {
|
|
96
|
+
case "--file":
|
|
97
|
+
if (!value || value.startsWith("-")) {
|
|
98
|
+
throw new Error("Option '--file' argument missing");
|
|
99
|
+
}
|
|
100
|
+
flags.file = value;
|
|
101
|
+
i += 1;
|
|
102
|
+
break;
|
|
103
|
+
case "--sample":
|
|
104
|
+
if (!value || value.startsWith("-")) {
|
|
105
|
+
throw new Error("Option '--sample' argument missing");
|
|
106
|
+
}
|
|
107
|
+
flags.sample = value;
|
|
108
|
+
i += 1;
|
|
109
|
+
break;
|
|
110
|
+
case "--attempt":
|
|
111
|
+
if (!value || value.startsWith("-")) {
|
|
112
|
+
throw new Error("Option '--attempt' argument missing");
|
|
113
|
+
}
|
|
114
|
+
flags.attempt = parseAttempt(value);
|
|
115
|
+
i += 1;
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
if (arg?.startsWith("-")) {
|
|
119
|
+
throw new Error(`Unknown option '${arg}'`);
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return flags;
|
|
125
|
+
}
|
|
126
|
+
function parseAttempt(value) {
|
|
127
|
+
const parsed = Number.parseInt(value, 10);
|
|
128
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
129
|
+
throw new Error("Option '--attempt' must be a positive integer");
|
|
130
|
+
}
|
|
131
|
+
return parsed;
|
|
132
|
+
}
|
|
133
|
+
function printWorkflowUsage() {
|
|
134
|
+
process.stdout.write(`Usage: gh-symphony workflow <command> [options]
|
|
135
|
+
|
|
136
|
+
Commands:
|
|
137
|
+
init Generate WORKFLOW.md and workflow support files
|
|
138
|
+
validate Parse and strictly validate a WORKFLOW.md file
|
|
139
|
+
preview Render the final worker prompt from a sample issue
|
|
140
|
+
|
|
141
|
+
Options:
|
|
142
|
+
workflow init [--non-interactive] [--project <id>] [--output <path>] [--skip-skills] [--skip-context] [--dry-run]
|
|
143
|
+
workflow validate [--file <path>]
|
|
144
|
+
workflow preview [--file <path>] [--sample <json>] [--attempt <n>]
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
147
|
+
async function loadWorkflowMarkdown(workflowPath) {
|
|
148
|
+
const resolvedPath = resolve(workflowPath ?? "WORKFLOW.md");
|
|
149
|
+
const markdown = await readFile(resolvedPath, "utf8");
|
|
150
|
+
return {
|
|
151
|
+
workflowPath: resolvedPath,
|
|
152
|
+
markdown
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function normalizeIssue(value) {
|
|
156
|
+
if (!value || typeof value !== "object") {
|
|
157
|
+
throw new Error("Sample JSON must be an object.");
|
|
158
|
+
}
|
|
159
|
+
const record = value;
|
|
160
|
+
const repositoryRecord = asRecord(record.repository, "repository");
|
|
161
|
+
const repositoryOwner = readRequiredString(
|
|
162
|
+
repositoryRecord.owner,
|
|
163
|
+
"repository.owner"
|
|
164
|
+
);
|
|
165
|
+
const repositoryName = readRequiredString(
|
|
166
|
+
repositoryRecord.name,
|
|
167
|
+
"repository.name"
|
|
168
|
+
);
|
|
169
|
+
const repositoryUrl = readOptionalString(repositoryRecord.url, "repository.url");
|
|
170
|
+
return {
|
|
171
|
+
id: readRequiredString(record.id, "id"),
|
|
172
|
+
identifier: readRequiredString(record.identifier, "identifier"),
|
|
173
|
+
number: readRequiredNumber(record.number, "number"),
|
|
174
|
+
title: readRequiredString(record.title, "title"),
|
|
175
|
+
description: readOptionalString(record.description, "description"),
|
|
176
|
+
priority: readOptionalNumber(record.priority, "priority"),
|
|
177
|
+
state: readRequiredString(record.state, "state"),
|
|
178
|
+
branchName: readOptionalString(
|
|
179
|
+
record.branchName ?? record.branch_name,
|
|
180
|
+
"branchName/branch_name"
|
|
181
|
+
),
|
|
182
|
+
url: readOptionalString(record.url, "url"),
|
|
183
|
+
labels: readStringArray(record.labels, "labels"),
|
|
184
|
+
blockedBy: readBlockers(record.blockedBy ?? record.blocked_by),
|
|
185
|
+
createdAt: readOptionalString(
|
|
186
|
+
record.createdAt ?? record.created_at,
|
|
187
|
+
"createdAt/created_at"
|
|
188
|
+
),
|
|
189
|
+
updatedAt: readOptionalString(
|
|
190
|
+
record.updatedAt ?? record.updated_at,
|
|
191
|
+
"updatedAt/updated_at"
|
|
192
|
+
),
|
|
193
|
+
repository: {
|
|
194
|
+
owner: repositoryOwner,
|
|
195
|
+
name: repositoryName,
|
|
196
|
+
cloneUrl: readOptionalString(repositoryRecord.cloneUrl, "repository.cloneUrl") ?? `https://github.com/${repositoryOwner}/${repositoryName}.git`,
|
|
197
|
+
...repositoryUrl ? { url: repositoryUrl } : {}
|
|
198
|
+
},
|
|
199
|
+
tracker: {
|
|
200
|
+
adapter: "github-project",
|
|
201
|
+
bindingId: "preview-sample",
|
|
202
|
+
itemId: readOptionalString(record.itemId, "itemId") ?? "preview-sample"
|
|
203
|
+
},
|
|
204
|
+
metadata: {}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function asRecord(value, field) {
|
|
208
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
209
|
+
throw new Error(`Sample JSON field '${field}' must be an object.`);
|
|
210
|
+
}
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
function readRequiredString(value, field) {
|
|
214
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
215
|
+
throw new Error(`Sample JSON field '${field}' must be a non-empty string.`);
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
function readOptionalString(value, field) {
|
|
220
|
+
if (value === null || value === void 0 || value === "") {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (typeof value !== "string") {
|
|
224
|
+
throw new Error(`Sample JSON field '${field}' must be a string.`);
|
|
225
|
+
}
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
function readRequiredNumber(value, field) {
|
|
229
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
230
|
+
throw new Error(`Sample JSON field '${field}' must be a number.`);
|
|
231
|
+
}
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
function readOptionalNumber(value, field) {
|
|
235
|
+
if (value === null || value === void 0 || value === "") {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
239
|
+
throw new Error(`Sample JSON field '${field}' must be a number.`);
|
|
240
|
+
}
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
function readStringArray(value, field) {
|
|
244
|
+
if (value === void 0) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
248
|
+
throw new Error(`Sample JSON field '${field}' must be an array of strings.`);
|
|
249
|
+
}
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
function readBlockers(value) {
|
|
253
|
+
if (value === void 0) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
if (!Array.isArray(value)) {
|
|
257
|
+
throw new Error("Sample JSON field 'blockedBy/blocked_by' must be an array.");
|
|
258
|
+
}
|
|
259
|
+
return value.map((entry, index) => {
|
|
260
|
+
const record = asRecord(entry, `blockedBy/blocked_by[${index}]`);
|
|
261
|
+
return {
|
|
262
|
+
id: readOptionalString(record.id, `blockedBy/blocked_by[${index}].id`),
|
|
263
|
+
identifier: readOptionalString(
|
|
264
|
+
record.identifier,
|
|
265
|
+
`blockedBy/blocked_by[${index}].identifier`
|
|
266
|
+
),
|
|
267
|
+
state: readOptionalString(
|
|
268
|
+
record.state,
|
|
269
|
+
`blockedBy/blocked_by[${index}].state`
|
|
270
|
+
)
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function validateContinuationGuidance(template) {
|
|
275
|
+
if (template.includes("{%") || template.includes("%}")) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
"template_parse_error: continuation guidance does not support Liquid tags."
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const pattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*\}\}/g;
|
|
281
|
+
let rendered = "";
|
|
282
|
+
let lastIndex = 0;
|
|
283
|
+
for (const match of template.matchAll(pattern)) {
|
|
284
|
+
const expression = match[1];
|
|
285
|
+
const index = match.index ?? 0;
|
|
286
|
+
rendered += template.slice(lastIndex, index);
|
|
287
|
+
if (!(expression in SAMPLE_CONTINUATION_VARIABLES)) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`template_render_error: unsupported continuation guidance variable '${expression}'.`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
rendered += String(
|
|
293
|
+
SAMPLE_CONTINUATION_VARIABLES[expression]
|
|
294
|
+
);
|
|
295
|
+
lastIndex = index + match[0].length;
|
|
296
|
+
}
|
|
297
|
+
rendered += template.slice(lastIndex);
|
|
298
|
+
const strayLiquidExpression = rendered.match(/\{\{[^}]*\}\}/);
|
|
299
|
+
if (strayLiquidExpression) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`template_parse_error: invalid continuation guidance expression '${strayLiquidExpression[0]}'.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async function loadSampleIssue(samplePath) {
|
|
306
|
+
if (!samplePath) {
|
|
307
|
+
return { issue: SAMPLE_ISSUE, sampleSource: "built-in" };
|
|
308
|
+
}
|
|
309
|
+
const resolvedPath = resolve(samplePath);
|
|
310
|
+
const raw = await readFile(resolvedPath, "utf8");
|
|
311
|
+
return {
|
|
312
|
+
issue: normalizeIssue(JSON.parse(raw)),
|
|
313
|
+
sampleSource: resolvedPath
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function validateWorkflow(workflowPath, markdown) {
|
|
317
|
+
const workflow = parseWorkflowMarkdown(markdown);
|
|
318
|
+
const promptFreshVariables = buildPromptVariables(SAMPLE_ISSUE, {
|
|
319
|
+
attempt: null
|
|
320
|
+
});
|
|
321
|
+
const promptRetryVariables = buildPromptVariables(SAMPLE_ISSUE, {
|
|
322
|
+
attempt: 2
|
|
323
|
+
});
|
|
324
|
+
renderPrompt(workflow.promptTemplate, promptFreshVariables, { strict: true });
|
|
325
|
+
renderPrompt(workflow.promptTemplate, promptRetryVariables, { strict: true });
|
|
326
|
+
const continuationGuidanceStatus = workflow.continuationGuidance ? (() => {
|
|
327
|
+
validateContinuationGuidance(workflow.continuationGuidance);
|
|
328
|
+
return "pass";
|
|
329
|
+
})() : "skip";
|
|
330
|
+
return {
|
|
331
|
+
ok: true,
|
|
332
|
+
workflowPath,
|
|
333
|
+
format: workflow.format,
|
|
334
|
+
checks: {
|
|
335
|
+
promptFresh: "pass",
|
|
336
|
+
promptRetry: "pass",
|
|
337
|
+
continuationGuidance: continuationGuidanceStatus
|
|
338
|
+
},
|
|
339
|
+
summary: {
|
|
340
|
+
trackerKind: workflow.tracker.kind,
|
|
341
|
+
githubProjectId: workflow.githubProjectId,
|
|
342
|
+
stateFieldName: workflow.lifecycle.stateFieldName,
|
|
343
|
+
activeStates: workflow.lifecycle.activeStates,
|
|
344
|
+
terminalStates: workflow.lifecycle.terminalStates,
|
|
345
|
+
blockerCheckStates: workflow.lifecycle.blockerCheckStates,
|
|
346
|
+
pollingIntervalMs: workflow.polling.intervalMs,
|
|
347
|
+
workspaceRoot: workflow.workspace.root,
|
|
348
|
+
agentCommand: workflow.agentCommand,
|
|
349
|
+
maxConcurrentAgents: workflow.agent.maxConcurrentAgents,
|
|
350
|
+
maxFailureRetries: workflow.agent.maxFailureRetries,
|
|
351
|
+
maxTurns: workflow.agent.maxTurns,
|
|
352
|
+
retryBaseDelayMs: workflow.agent.retryBaseDelayMs,
|
|
353
|
+
maxRetryBackoffMs: workflow.agent.maxRetryBackoffMs,
|
|
354
|
+
codex: {
|
|
355
|
+
approvalPolicy: workflow.codex.approvalPolicy,
|
|
356
|
+
threadSandbox: workflow.codex.threadSandbox,
|
|
357
|
+
turnSandboxPolicy: workflow.codex.turnSandboxPolicy,
|
|
358
|
+
readTimeoutMs: workflow.codex.readTimeoutMs,
|
|
359
|
+
stallTimeoutMs: workflow.codex.stallTimeoutMs,
|
|
360
|
+
turnTimeoutMs: workflow.codex.turnTimeoutMs
|
|
361
|
+
},
|
|
362
|
+
hooks: {
|
|
363
|
+
afterCreate: workflow.hooks.afterCreate,
|
|
364
|
+
beforeRun: workflow.hooks.beforeRun,
|
|
365
|
+
afterRun: workflow.hooks.afterRun,
|
|
366
|
+
beforeRemove: workflow.hooks.beforeRemove,
|
|
367
|
+
timeoutMs: workflow.hooks.timeoutMs
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function printValidationReport(report) {
|
|
373
|
+
process.stdout.write(`WORKFLOW.md validation passed
|
|
374
|
+
Path: ${report.workflowPath}
|
|
375
|
+
Format: ${report.format}
|
|
376
|
+
Prompt checks: fresh=pass, retry=pass, continuation_guidance=${report.checks.continuationGuidance}
|
|
377
|
+
|
|
378
|
+
Lifecycle
|
|
379
|
+
tracker.kind=${report.summary.trackerKind ?? "unset"}
|
|
380
|
+
tracker.project_id=${report.summary.githubProjectId ?? "unset"}
|
|
381
|
+
tracker.state_field=${report.summary.stateFieldName}
|
|
382
|
+
active_states=${report.summary.activeStates.join(", ") || "(none)"}
|
|
383
|
+
terminal_states=${report.summary.terminalStates.join(", ") || "(none)"}
|
|
384
|
+
blocker_check_states=${report.summary.blockerCheckStates.join(", ") || "(none)"}
|
|
385
|
+
|
|
386
|
+
Runtime
|
|
387
|
+
polling.interval_ms=${report.summary.pollingIntervalMs}
|
|
388
|
+
workspace.root=${report.summary.workspaceRoot ?? "unset"}
|
|
389
|
+
codex.command=${report.summary.agentCommand}
|
|
390
|
+
agent.max_concurrent_agents=${report.summary.maxConcurrentAgents}
|
|
391
|
+
agent.max_failure_retries=${report.summary.maxFailureRetries}
|
|
392
|
+
agent.max_turns=${report.summary.maxTurns}
|
|
393
|
+
agent.retry_base_delay_ms=${report.summary.retryBaseDelayMs}
|
|
394
|
+
agent.max_retry_backoff_ms=${report.summary.maxRetryBackoffMs}
|
|
395
|
+
codex.approval_policy=${report.summary.codex.approvalPolicy ?? "unset"}
|
|
396
|
+
codex.thread_sandbox=${report.summary.codex.threadSandbox ?? "unset"}
|
|
397
|
+
codex.turn_sandbox_policy=${report.summary.codex.turnSandboxPolicy ?? "unset"}
|
|
398
|
+
codex.read_timeout_ms=${report.summary.codex.readTimeoutMs}
|
|
399
|
+
codex.stall_timeout_ms=${report.summary.codex.stallTimeoutMs}
|
|
400
|
+
codex.turn_timeout_ms=${report.summary.codex.turnTimeoutMs}
|
|
401
|
+
|
|
402
|
+
Hooks
|
|
403
|
+
after_create=${report.summary.hooks.afterCreate ?? "unset"}
|
|
404
|
+
before_run=${report.summary.hooks.beforeRun ?? "unset"}
|
|
405
|
+
after_run=${report.summary.hooks.afterRun ?? "unset"}
|
|
406
|
+
before_remove=${report.summary.hooks.beforeRemove ?? "unset"}
|
|
407
|
+
hooks.timeout_ms=${report.summary.hooks.timeoutMs}
|
|
408
|
+
`);
|
|
409
|
+
}
|
|
410
|
+
async function runValidate(args, options) {
|
|
411
|
+
const flags = parseValidateFlags(args);
|
|
412
|
+
const { workflowPath, markdown } = await loadWorkflowMarkdown(flags.file);
|
|
413
|
+
const report = validateWorkflow(workflowPath, markdown);
|
|
414
|
+
if (options.json) {
|
|
415
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}
|
|
416
|
+
`);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
printValidationReport(report);
|
|
420
|
+
}
|
|
421
|
+
async function runPreview(args, options) {
|
|
422
|
+
const flags = parsePreviewFlags(args);
|
|
423
|
+
const { workflowPath, markdown } = await loadWorkflowMarkdown(flags.file);
|
|
424
|
+
const workflow = parseWorkflowMarkdown(markdown);
|
|
425
|
+
const { issue, sampleSource } = await loadSampleIssue(flags.sample);
|
|
426
|
+
const variables = buildPromptVariables(issue, {
|
|
427
|
+
attempt: flags.attempt
|
|
428
|
+
});
|
|
429
|
+
const renderedPrompt = renderPrompt(workflow.promptTemplate, variables, {
|
|
430
|
+
strict: true
|
|
431
|
+
});
|
|
432
|
+
if (options.json) {
|
|
433
|
+
process.stdout.write(
|
|
434
|
+
`${JSON.stringify(
|
|
435
|
+
{
|
|
436
|
+
workflowPath,
|
|
437
|
+
sampleSource,
|
|
438
|
+
attempt: flags.attempt,
|
|
439
|
+
renderedPrompt
|
|
440
|
+
},
|
|
441
|
+
null,
|
|
442
|
+
2
|
|
443
|
+
)}
|
|
444
|
+
`
|
|
445
|
+
);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
process.stdout.write(`WORKFLOW.md prompt preview
|
|
449
|
+
Path: ${workflowPath}
|
|
450
|
+
Sample: ${sampleSource}
|
|
451
|
+
Attempt: ${flags.attempt ?? "fresh"}
|
|
452
|
+
|
|
453
|
+
${renderedPrompt}
|
|
454
|
+
`);
|
|
455
|
+
}
|
|
456
|
+
var handler = async (args, options) => {
|
|
457
|
+
const parsed = parseWorkflowArgs(args);
|
|
458
|
+
if (parsed.error) {
|
|
459
|
+
process.stderr.write(`${parsed.error}
|
|
460
|
+
`);
|
|
461
|
+
printWorkflowUsage();
|
|
462
|
+
process.exitCode = 1;
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (parsed.args[0] === "--help" || parsed.args[0] === "-h") {
|
|
466
|
+
printWorkflowUsage();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (!parsed.subcommand) {
|
|
470
|
+
process.stderr.write("Missing workflow subcommand.\n");
|
|
471
|
+
printWorkflowUsage();
|
|
472
|
+
process.exitCode = 1;
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
switch (parsed.subcommand) {
|
|
477
|
+
case "init":
|
|
478
|
+
await init_default(parsed.args, options);
|
|
479
|
+
return;
|
|
480
|
+
case "validate":
|
|
481
|
+
await runValidate(parsed.args, options);
|
|
482
|
+
return;
|
|
483
|
+
case "preview":
|
|
484
|
+
await runPreview(parsed.args, options);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
489
|
+
process.stderr.write(`Workflow command failed: ${message}
|
|
490
|
+
`);
|
|
491
|
+
process.exitCode = 1;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var workflow_default = handler;
|
|
495
|
+
export {
|
|
496
|
+
workflow_default as default
|
|
497
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gh-symphony/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "hojinzs",
|
|
6
6
|
"description": "Interactive CLI for GitHub Symphony orchestration",
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"@gh-symphony/core": "0.0.14",
|
|
45
|
-
"@gh-symphony/dashboard": "0.0.14",
|
|
46
|
-
"@gh-symphony/tracker-github": "0.0.14",
|
|
47
45
|
"@gh-symphony/orchestrator": "0.0.14",
|
|
48
|
-
"@gh-symphony/
|
|
46
|
+
"@gh-symphony/dashboard": "0.0.14",
|
|
47
|
+
"@gh-symphony/worker": "0.0.14",
|
|
48
|
+
"@gh-symphony/tracker-github": "0.0.14"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "tsup",
|
package/dist/chunk-7UBUBSMH.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/github/gh-auth.ts
|
|
4
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
5
|
-
var REQUIRED_GH_SCOPES = ["repo", "read:org", "project"];
|
|
6
|
-
var GhAuthError = class extends Error {
|
|
7
|
-
constructor(code, message) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.code = code;
|
|
10
|
-
this.name = "GhAuthError";
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
function checkGhInstalled(opts) {
|
|
14
|
-
const execImpl = opts?.execImpl ?? execFileSync;
|
|
15
|
-
try {
|
|
16
|
-
execImpl("gh", ["--version"], { stdio: "pipe" });
|
|
17
|
-
return true;
|
|
18
|
-
} catch (error) {
|
|
19
|
-
const execError = error;
|
|
20
|
-
if (execError.code === "ENOENT") {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function checkGhAuthenticated(opts) {
|
|
27
|
-
const spawnImpl = opts?.spawnImpl ?? spawnSync;
|
|
28
|
-
const result = spawnImpl("gh", ["auth", "status"], {
|
|
29
|
-
encoding: "utf8",
|
|
30
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31
|
-
});
|
|
32
|
-
if ((result.status ?? 1) !== 0) {
|
|
33
|
-
return { authenticated: false };
|
|
34
|
-
}
|
|
35
|
-
const login = parseLogin((result.stdout ?? "").toString());
|
|
36
|
-
return { authenticated: true, login };
|
|
37
|
-
}
|
|
38
|
-
function checkGhScopes(opts) {
|
|
39
|
-
const spawnImpl = opts?.spawnImpl ?? spawnSync;
|
|
40
|
-
const result = spawnImpl("gh", ["auth", "status"], {
|
|
41
|
-
encoding: "utf8",
|
|
42
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
43
|
-
});
|
|
44
|
-
const output = (result.stdout ?? "").toString();
|
|
45
|
-
const scopes = parseScopes(output);
|
|
46
|
-
if (scopes.length === 0) {
|
|
47
|
-
return { valid: true, missing: [], scopes: [] };
|
|
48
|
-
}
|
|
49
|
-
const normalized = scopes.map((scope) => scope.toLowerCase());
|
|
50
|
-
const missing = REQUIRED_GH_SCOPES.filter(
|
|
51
|
-
(scope) => !normalized.includes(scope)
|
|
52
|
-
);
|
|
53
|
-
return {
|
|
54
|
-
valid: missing.length === 0,
|
|
55
|
-
missing: [...missing],
|
|
56
|
-
scopes
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function getGhToken(opts) {
|
|
60
|
-
if (process.env.GITHUB_GRAPHQL_TOKEN) {
|
|
61
|
-
return process.env.GITHUB_GRAPHQL_TOKEN;
|
|
62
|
-
}
|
|
63
|
-
const execImpl = opts?.execImpl ?? execFileSync;
|
|
64
|
-
try {
|
|
65
|
-
const token = execImpl("gh", ["auth", "token"], {
|
|
66
|
-
encoding: "utf8",
|
|
67
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
68
|
-
}).toString().trim();
|
|
69
|
-
if (!token) {
|
|
70
|
-
throw new GhAuthError(
|
|
71
|
-
"token_failed",
|
|
72
|
-
"gh auth token \uC2E4\uD328. gh auth status \uB97C \uD655\uC778\uD558\uC138\uC694."
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
return token;
|
|
76
|
-
} catch (error) {
|
|
77
|
-
if (error instanceof GhAuthError) {
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
throw new GhAuthError(
|
|
81
|
-
"token_failed",
|
|
82
|
-
"gh auth token \uC2E4\uD328. gh auth status \uB97C \uD655\uC778\uD558\uC138\uC694."
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function ensureGhAuth(opts) {
|
|
87
|
-
const execImpl = opts?.execImpl ?? execFileSync;
|
|
88
|
-
const spawnImpl = opts?.spawnImpl ?? spawnSync;
|
|
89
|
-
if (!checkGhInstalled({ execImpl })) {
|
|
90
|
-
throw new GhAuthError(
|
|
91
|
-
"not_installed",
|
|
92
|
-
"gh CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. https://cli.github.com \uC5D0\uC11C \uC124\uCE58\uD558\uC138\uC694."
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
const auth = checkGhAuthenticated({ spawnImpl });
|
|
96
|
-
if (!auth.authenticated) {
|
|
97
|
-
throw new GhAuthError(
|
|
98
|
-
"not_authenticated",
|
|
99
|
-
"gh auth login --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
const scopeCheck = checkGhScopes({ spawnImpl });
|
|
103
|
-
if (!scopeCheck.valid) {
|
|
104
|
-
throw new GhAuthError(
|
|
105
|
-
"missing_scopes",
|
|
106
|
-
`gh auth refresh --scopes repo,read:org,project \uB97C \uC2E4\uD589\uD558\uC138\uC694. (missing: ${scopeCheck.missing.join(", ")})`
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
const token = getGhToken({ execImpl });
|
|
110
|
-
return { login: auth.login ?? "unknown", token };
|
|
111
|
-
}
|
|
112
|
-
function parseLogin(output) {
|
|
113
|
-
const matched = output.match(
|
|
114
|
-
/Logged in to github\.com account\s+\*?\*?([A-Za-z0-9_-]+)\*?\*?/i
|
|
115
|
-
);
|
|
116
|
-
return matched?.[1];
|
|
117
|
-
}
|
|
118
|
-
function parseScopes(output) {
|
|
119
|
-
const matched = output.match(/Token scopes:\s*(.+)/i);
|
|
120
|
-
if (!matched) {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
return matched[1].split(",").map((scope) => scope.trim().replace(/^'+|'+$/g, "")).filter((scope) => scope.length > 0);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export {
|
|
127
|
-
REQUIRED_GH_SCOPES,
|
|
128
|
-
GhAuthError,
|
|
129
|
-
checkGhInstalled,
|
|
130
|
-
checkGhAuthenticated,
|
|
131
|
-
checkGhScopes,
|
|
132
|
-
getGhToken,
|
|
133
|
-
ensureGhAuth
|
|
134
|
-
};
|