@gh-symphony/cli 0.0.17 → 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 +105 -9
- package/dist/{chunk-EFMFGOWM.js → chunk-6CI3UUMH.js} +282 -57
- package/dist/chunk-C7G7RJ4G.js +146 -0
- package/dist/{chunk-MHIWAIVD.js → chunk-GKENCODJ.js} +141 -53
- package/dist/{project-557FE2GD.js → chunk-H2YXSYOZ.js} +108 -92
- package/dist/{chunk-TF3QNWNC.js → chunk-M3IFVLQS.js} +246 -212
- package/dist/{chunk-IWR4UQEJ.js → chunk-RN2PACNV.js} +350 -523
- package/dist/chunk-TILHWBP6.js +638 -0
- package/dist/{chunk-6HBZC3BE.js → chunk-XN5ABWZ6.js} +23 -5
- package/dist/{chunk-76QPITKI.js → chunk-Y6TYJMNT.js} +1 -1
- package/dist/{config-cmd-AZ7POMAA.js → config-cmd-DNXNL26Z.js} +3 -1
- package/dist/doctor-IYHCFXOZ.js +1126 -0
- package/dist/index.js +157 -19
- package/dist/init-KZT6YNOH.js +33 -0
- package/dist/{logs-6LNGT2GF.js → logs-6JKKYDGJ.js} +1 -1
- package/dist/project-DNALEWO3.js +22 -0
- package/dist/{recover-LVBI2TGH.js → recover-C3V2QAUB.js} +3 -3
- package/dist/repo-HDDE7OUI.js +321 -0
- package/dist/{run-WITYAYFZ.js → run-XI2S5Y4V.js} +3 -3
- package/dist/setup-K4CYYJBF.js +431 -0
- package/dist/{start-JUFKNL3N.js → start-M6IQGRFO.js} +5 -5
- package/dist/{status-3WK5BWRZ.js → status-QSCFVGRQ.js} +2 -2
- package/dist/{stop-AA3AP5M6.js → stop-7MFCBQVW.js} +2 -2
- package/dist/upgrade-F4VE4XBS.js +165 -0
- package/dist/{version-YVM2A25J.js → version-Y5RYNWMF.js} +1 -1
- package/dist/worker-entry.js +39 -11
- package/dist/workflow-TBIFY5MO.js +497 -0
- package/package.json +4 -4
- package/dist/chunk-JO3AXHQI.js +0 -130
- package/dist/chunk-TH5QPO3Y.js +0 -67
- package/dist/init-EZXQAXZM.js +0 -17
- package/dist/repo-R3XBIVAX.js +0 -121
package/dist/worker-entry.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
classifySessionExit,
|
|
4
4
|
parseWorkflowMarkdown,
|
|
5
5
|
readEnvFile
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-M3IFVLQS.js";
|
|
7
7
|
|
|
8
8
|
// ../worker/dist/index.js
|
|
9
9
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -668,7 +668,29 @@ function normalizeContinuationVariable(value) {
|
|
|
668
668
|
return normalized ? normalized : null;
|
|
669
669
|
}
|
|
670
670
|
function renderContinuationGuidance(template, variables) {
|
|
671
|
-
|
|
671
|
+
if (template.includes("{%") || template.includes("%}")) {
|
|
672
|
+
throw new Error("template_parse_error: continuation guidance does not support Liquid tags.");
|
|
673
|
+
}
|
|
674
|
+
let rendered = "";
|
|
675
|
+
let lastIndex = 0;
|
|
676
|
+
const pattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*\}\}/g;
|
|
677
|
+
for (const match of template.matchAll(pattern)) {
|
|
678
|
+
const matchedText = match[0];
|
|
679
|
+
const expression = match[1];
|
|
680
|
+
const index = match.index ?? 0;
|
|
681
|
+
rendered += template.slice(lastIndex, index);
|
|
682
|
+
if (!(expression in variables)) {
|
|
683
|
+
throw new Error(`template_render_error: unsupported continuation guidance variable '${expression}'.`);
|
|
684
|
+
}
|
|
685
|
+
rendered += variables[expression] ?? "";
|
|
686
|
+
lastIndex = index + matchedText.length;
|
|
687
|
+
}
|
|
688
|
+
rendered += template.slice(lastIndex);
|
|
689
|
+
const strayLiquidExpression = rendered.match(/\{\{[^}]*\}\}/);
|
|
690
|
+
if (strayLiquidExpression) {
|
|
691
|
+
throw new Error(`template_parse_error: invalid continuation guidance expression '${strayLiquidExpression[0]}'.`);
|
|
692
|
+
}
|
|
693
|
+
return rendered;
|
|
672
694
|
}
|
|
673
695
|
|
|
674
696
|
// ../worker/dist/token-usage.js
|
|
@@ -770,7 +792,7 @@ function shutdown(signal) {
|
|
|
770
792
|
}
|
|
771
793
|
stopOrchestratorHeartbeatTimer();
|
|
772
794
|
emitOrchestratorHeartbeat();
|
|
773
|
-
await
|
|
795
|
+
await persistSessionTokenUsageArtifact(launcherEnv);
|
|
774
796
|
await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
|
|
775
797
|
console.log(`Worker stopped on ${signal}`);
|
|
776
798
|
process.exit(0);
|
|
@@ -854,7 +876,7 @@ function emitOrchestratorHeartbeat() {
|
|
|
854
876
|
type: "heartbeat",
|
|
855
877
|
issueId,
|
|
856
878
|
lastEventAt: runtimeState.lastEventAt,
|
|
857
|
-
tokenUsage:
|
|
879
|
+
tokenUsage: resolveSessionTokenUsageDelta(),
|
|
858
880
|
rateLimits: runtimeState.rateLimits ? { ...runtimeState.rateLimits } : null,
|
|
859
881
|
sessionInfo: { ...runtimeState.sessionInfo },
|
|
860
882
|
executionPhase: runtimeState.executionPhase,
|
|
@@ -890,7 +912,7 @@ function emitOrchestratorChannelEvent(event) {
|
|
|
890
912
|
type: "codex_update",
|
|
891
913
|
issueId,
|
|
892
914
|
lastEventAt,
|
|
893
|
-
tokenUsage:
|
|
915
|
+
tokenUsage: resolveSessionTokenUsageDelta(),
|
|
894
916
|
sessionInfo: { ...runtimeState.sessionInfo },
|
|
895
917
|
executionPhase: runtimeState.executionPhase,
|
|
896
918
|
runPhase: runtimeState.runPhase,
|
|
@@ -915,6 +937,12 @@ function resolveTurnTokenUsageDelta(baseline) {
|
|
|
915
937
|
totalTokens: Math.max(0, runtimeState.tokenUsage.totalTokens - baseline.totalTokens)
|
|
916
938
|
};
|
|
917
939
|
}
|
|
940
|
+
function resolveSessionTokenUsageDelta() {
|
|
941
|
+
return resolveTurnTokenUsageDelta(sessionBudgetState.tokenUsageBaseline);
|
|
942
|
+
}
|
|
943
|
+
async function persistSessionTokenUsageArtifact(env) {
|
|
944
|
+
await persistTokenUsageArtifact(env, resolveSessionTokenUsageDelta());
|
|
945
|
+
}
|
|
918
946
|
function emitTurnStartedEvent(turn) {
|
|
919
947
|
const issueId = runtimeState.run?.issueId;
|
|
920
948
|
if (!issueId) {
|
|
@@ -1014,7 +1042,7 @@ async function startAssignedRun() {
|
|
|
1014
1042
|
runtimeState.run.lastError = code === 0 && !signal ? null : `codex app-server exited with ${signal ?? code ?? "unknown"}`;
|
|
1015
1043
|
}
|
|
1016
1044
|
}
|
|
1017
|
-
void
|
|
1045
|
+
void persistSessionTokenUsageArtifact(launcherEnv);
|
|
1018
1046
|
});
|
|
1019
1047
|
childProcess.once("error", (error) => {
|
|
1020
1048
|
runtimeState.status = "failed";
|
|
@@ -1022,7 +1050,7 @@ async function startAssignedRun() {
|
|
|
1022
1050
|
if (runtimeState.run) {
|
|
1023
1051
|
runtimeState.run.lastError = error.message;
|
|
1024
1052
|
}
|
|
1025
|
-
void
|
|
1053
|
+
void persistSessionTokenUsageArtifact(launcherEnv);
|
|
1026
1054
|
});
|
|
1027
1055
|
} catch (error) {
|
|
1028
1056
|
runtimeState.status = "failed";
|
|
@@ -1030,7 +1058,7 @@ async function startAssignedRun() {
|
|
|
1030
1058
|
if (runtimeState.run) {
|
|
1031
1059
|
runtimeState.run.lastError = error instanceof Error ? error.message : "Unknown worker startup error";
|
|
1032
1060
|
}
|
|
1033
|
-
await
|
|
1061
|
+
await persistSessionTokenUsageArtifact(launcherEnv);
|
|
1034
1062
|
}
|
|
1035
1063
|
}
|
|
1036
1064
|
async function runCodexClientProtocol(child, plan, env, options) {
|
|
@@ -1414,7 +1442,7 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1414
1442
|
});
|
|
1415
1443
|
stopOrchestratorHeartbeatTimer();
|
|
1416
1444
|
emitOrchestratorHeartbeat();
|
|
1417
|
-
await
|
|
1445
|
+
await persistSessionTokenUsageArtifact(env);
|
|
1418
1446
|
await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
|
|
1419
1447
|
setTimeout(() => {
|
|
1420
1448
|
process.exit(0);
|
|
@@ -1600,7 +1628,7 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1600
1628
|
});
|
|
1601
1629
|
stopOrchestratorHeartbeatTimer();
|
|
1602
1630
|
emitOrchestratorHeartbeat();
|
|
1603
|
-
await
|
|
1631
|
+
await persistSessionTokenUsageArtifact(env);
|
|
1604
1632
|
await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
|
|
1605
1633
|
setTimeout(() => {
|
|
1606
1634
|
process.exit(userInputRequired || turnTerminalFailurePhase ? 1 : 0);
|
|
@@ -1638,7 +1666,7 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1638
1666
|
}
|
|
1639
1667
|
stopOrchestratorHeartbeatTimer();
|
|
1640
1668
|
emitOrchestratorHeartbeat();
|
|
1641
|
-
await
|
|
1669
|
+
await persistSessionTokenUsageArtifact(env);
|
|
1642
1670
|
await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
|
|
1643
1671
|
setTimeout(() => {
|
|
1644
1672
|
process.exit(1);
|
|
@@ -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/tracker-github": "0.0.14",
|
|
46
|
-
"@gh-symphony/dashboard": "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",
|