@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.
Files changed (32) hide show
  1. package/README.md +105 -9
  2. package/dist/{chunk-EFMFGOWM.js → chunk-6CI3UUMH.js} +282 -57
  3. package/dist/chunk-C7G7RJ4G.js +146 -0
  4. package/dist/{chunk-MHIWAIVD.js → chunk-GKENCODJ.js} +141 -53
  5. package/dist/{project-557FE2GD.js → chunk-H2YXSYOZ.js} +108 -92
  6. package/dist/{chunk-TF3QNWNC.js → chunk-M3IFVLQS.js} +246 -212
  7. package/dist/{chunk-IWR4UQEJ.js → chunk-RN2PACNV.js} +350 -523
  8. package/dist/chunk-TILHWBP6.js +638 -0
  9. package/dist/{chunk-6HBZC3BE.js → chunk-XN5ABWZ6.js} +23 -5
  10. package/dist/{chunk-76QPITKI.js → chunk-Y6TYJMNT.js} +1 -1
  11. package/dist/{config-cmd-AZ7POMAA.js → config-cmd-DNXNL26Z.js} +3 -1
  12. package/dist/doctor-IYHCFXOZ.js +1126 -0
  13. package/dist/index.js +157 -19
  14. package/dist/init-KZT6YNOH.js +33 -0
  15. package/dist/{logs-6LNGT2GF.js → logs-6JKKYDGJ.js} +1 -1
  16. package/dist/project-DNALEWO3.js +22 -0
  17. package/dist/{recover-LVBI2TGH.js → recover-C3V2QAUB.js} +3 -3
  18. package/dist/repo-HDDE7OUI.js +321 -0
  19. package/dist/{run-WITYAYFZ.js → run-XI2S5Y4V.js} +3 -3
  20. package/dist/setup-K4CYYJBF.js +431 -0
  21. package/dist/{start-JUFKNL3N.js → start-M6IQGRFO.js} +5 -5
  22. package/dist/{status-3WK5BWRZ.js → status-QSCFVGRQ.js} +2 -2
  23. package/dist/{stop-AA3AP5M6.js → stop-7MFCBQVW.js} +2 -2
  24. package/dist/upgrade-F4VE4XBS.js +165 -0
  25. package/dist/{version-YVM2A25J.js → version-Y5RYNWMF.js} +1 -1
  26. package/dist/worker-entry.js +39 -11
  27. package/dist/workflow-TBIFY5MO.js +497 -0
  28. package/package.json +4 -4
  29. package/dist/chunk-JO3AXHQI.js +0 -130
  30. package/dist/chunk-TH5QPO3Y.js +0 -67
  31. package/dist/init-EZXQAXZM.js +0 -17
  32. package/dist/repo-R3XBIVAX.js +0 -121
@@ -3,7 +3,7 @@ import {
3
3
  classifySessionExit,
4
4
  parseWorkflowMarkdown,
5
5
  readEnvFile
6
- } from "./chunk-TF3QNWNC.js";
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
- return template.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g, (match, key) => variables[key] ?? match);
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 persistTokenUsageArtifact(launcherEnv, runtimeState.tokenUsage);
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: { ...runtimeState.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: { ...runtimeState.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 persistTokenUsageArtifact(launcherEnv, runtimeState.tokenUsage);
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 persistTokenUsageArtifact(launcherEnv, runtimeState.tokenUsage);
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 persistTokenUsageArtifact(launcherEnv, runtimeState.tokenUsage);
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 persistTokenUsageArtifact(env, runtimeState.tokenUsage);
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 persistTokenUsageArtifact(env, runtimeState.tokenUsage);
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 persistTokenUsageArtifact(env, runtimeState.tokenUsage);
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.17",
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/worker": "0.0.14"
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",