@gh-symphony/cli 0.0.19 → 0.0.21

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.
@@ -1,13 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  init_default
4
- } from "./chunk-RN2PACNV.js";
4
+ } from "./chunk-JN3TQVFV.js";
5
+ import {
6
+ fetchGithubProjectIssueByRepositoryAndNumber
7
+ } from "./chunk-SXGT7LOF.js";
8
+ import {
9
+ GitHubApiError,
10
+ createClient,
11
+ findLinkedRepository,
12
+ getGhTokenWithSource,
13
+ getProjectDetail,
14
+ validateGitHubToken
15
+ } from "./chunk-C67H3OUL.js";
5
16
  import {
6
17
  buildPromptVariables,
7
18
  parseWorkflowMarkdown,
8
19
  renderPrompt
9
- } from "./chunk-M3IFVLQS.js";
10
- import "./chunk-TILHWBP6.js";
20
+ } from "./chunk-QEONJ5DZ.js";
21
+ import {
22
+ inspectManagedProjectSelection
23
+ } from "./chunk-C7G7RJ4G.js";
11
24
  import "./chunk-ROGRTUFI.js";
12
25
 
13
26
  // src/commands/workflow.ts
@@ -50,6 +63,25 @@ var SAMPLE_CONTINUATION_VARIABLES = {
50
63
  lastTurnSummary: "Validated the prompt template and updated the CLI routing.",
51
64
  cumulativeTurnCount: 3
52
65
  };
66
+ var workflowCommandDependencies = {
67
+ createGitHubClient: createClient,
68
+ fetchLiveIssue: fetchGithubProjectIssueByRepositoryAndNumber,
69
+ getGitHubProjectDetail: getProjectDetail,
70
+ getGitHubTokenWithSource: getGhTokenWithSource,
71
+ resolveManagedProjectSelection: inspectManagedProjectSelection,
72
+ validateGitHubToken
73
+ };
74
+ function setWorkflowCommandDependenciesForTest(overrides) {
75
+ Object.assign(workflowCommandDependencies, overrides);
76
+ }
77
+ function resetWorkflowCommandDependenciesForTest() {
78
+ workflowCommandDependencies.createGitHubClient = createClient;
79
+ workflowCommandDependencies.fetchLiveIssue = fetchGithubProjectIssueByRepositoryAndNumber;
80
+ workflowCommandDependencies.getGitHubProjectDetail = getProjectDetail;
81
+ workflowCommandDependencies.getGitHubTokenWithSource = getGhTokenWithSource;
82
+ workflowCommandDependencies.resolveManagedProjectSelection = inspectManagedProjectSelection;
83
+ workflowCommandDependencies.validateGitHubToken = validateGitHubToken;
84
+ }
53
85
  function parseWorkflowArgs(args) {
54
86
  const [subcommand, ...rest] = args;
55
87
  if (!subcommand) {
@@ -107,6 +139,21 @@ function parsePreviewFlags(args) {
107
139
  flags.sample = value;
108
140
  i += 1;
109
141
  break;
142
+ case "--issue":
143
+ if (!value || value.startsWith("-")) {
144
+ throw new Error("Option '--issue' argument missing");
145
+ }
146
+ flags.issue = value;
147
+ i += 1;
148
+ break;
149
+ case "--project-id":
150
+ case "--project":
151
+ if (!value || value.startsWith("-")) {
152
+ throw new Error(`Option '${arg}' argument missing`);
153
+ }
154
+ flags.projectId = value;
155
+ i += 1;
156
+ break;
110
157
  case "--attempt":
111
158
  if (!value || value.startsWith("-")) {
112
159
  throw new Error("Option '--attempt' argument missing");
@@ -136,12 +183,12 @@ function printWorkflowUsage() {
136
183
  Commands:
137
184
  init Generate WORKFLOW.md and workflow support files
138
185
  validate Parse and strictly validate a WORKFLOW.md file
139
- preview Render the final worker prompt from a sample issue
186
+ preview Render the final worker prompt from a sample or live issue
140
187
 
141
188
  Options:
142
189
  workflow init [--non-interactive] [--project <id>] [--output <path>] [--skip-skills] [--skip-context] [--dry-run]
143
190
  workflow validate [--file <path>]
144
- workflow preview [--file <path>] [--sample <json>] [--attempt <n>]
191
+ workflow preview [--file <path>] [--issue <owner/repo#number>] [--project-id <projectId>] [--sample <json>] [--attempt <n>]
145
192
  `);
146
193
  }
147
194
  async function loadWorkflowMarkdown(workflowPath) {
@@ -166,7 +213,10 @@ function normalizeIssue(value) {
166
213
  repositoryRecord.name,
167
214
  "repository.name"
168
215
  );
169
- const repositoryUrl = readOptionalString(repositoryRecord.url, "repository.url");
216
+ const repositoryUrl = readOptionalString(
217
+ repositoryRecord.url,
218
+ "repository.url"
219
+ );
170
220
  return {
171
221
  id: readRequiredString(record.id, "id"),
172
222
  identifier: readRequiredString(record.identifier, "identifier"),
@@ -245,7 +295,9 @@ function readStringArray(value, field) {
245
295
  return [];
246
296
  }
247
297
  if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
248
- throw new Error(`Sample JSON field '${field}' must be an array of strings.`);
298
+ throw new Error(
299
+ `Sample JSON field '${field}' must be an array of strings.`
300
+ );
249
301
  }
250
302
  return value;
251
303
  }
@@ -254,7 +306,9 @@ function readBlockers(value) {
254
306
  return [];
255
307
  }
256
308
  if (!Array.isArray(value)) {
257
- throw new Error("Sample JSON field 'blockedBy/blocked_by' must be an array.");
309
+ throw new Error(
310
+ "Sample JSON field 'blockedBy/blocked_by' must be an array."
311
+ );
258
312
  }
259
313
  return value.map((entry, index) => {
260
314
  const record = asRecord(entry, `blockedBy/blocked_by[${index}]`);
@@ -313,6 +367,106 @@ async function loadSampleIssue(samplePath) {
313
367
  sampleSource: resolvedPath
314
368
  };
315
369
  }
370
+ function parseIssueReference(value) {
371
+ const match = /^(?<owner>[A-Za-z0-9_.-]+)\/(?<name>[A-Za-z0-9_.-]+)#(?<number>\d+)$/.exec(
372
+ value.trim()
373
+ );
374
+ if (!match?.groups) {
375
+ throw new Error(
376
+ "Option '--issue' must be in the format 'owner/repo#number'."
377
+ );
378
+ }
379
+ return {
380
+ owner: match.groups.owner,
381
+ name: match.groups.name,
382
+ number: Number.parseInt(match.groups.number, 10),
383
+ identifier: `${match.groups.owner}/${match.groups.name}#${match.groups.number}`
384
+ };
385
+ }
386
+ function readGitHubProjectBinding(projectConfig) {
387
+ const bindingId = projectConfig.tracker.bindingId?.trim();
388
+ if (bindingId) {
389
+ return bindingId;
390
+ }
391
+ const settingsProjectId = projectConfig.tracker.settings?.projectId;
392
+ return typeof settingsProjectId === "string" && settingsProjectId.trim().length > 0 ? settingsProjectId.trim() : null;
393
+ }
394
+ function formatAuthError(error) {
395
+ return `GitHub authentication is required for live issue preview. ${error.message}`;
396
+ }
397
+ async function loadLiveIssue(issueReference, projectId, options) {
398
+ const issue = parseIssueReference(issueReference);
399
+ const selection = await workflowCommandDependencies.resolveManagedProjectSelection({
400
+ configDir: options.configDir,
401
+ requestedProjectId: projectId
402
+ });
403
+ if (selection.kind !== "resolved") {
404
+ throw new Error(selection.message);
405
+ }
406
+ const githubProjectId = readGitHubProjectBinding(selection.projectConfig);
407
+ if (!githubProjectId) {
408
+ throw new Error(
409
+ `Managed project "${selection.projectId}" is not bound to a GitHub Project. Re-run 'gh-symphony project add' and select a valid GitHub Project binding.`
410
+ );
411
+ }
412
+ let auth;
413
+ try {
414
+ const tokenResult = workflowCommandDependencies.getGitHubTokenWithSource();
415
+ auth = await workflowCommandDependencies.validateGitHubToken(
416
+ tokenResult.token,
417
+ tokenResult.source
418
+ );
419
+ } catch (error) {
420
+ if (error instanceof Error) {
421
+ throw new Error(formatAuthError(error));
422
+ }
423
+ throw error;
424
+ }
425
+ const client = workflowCommandDependencies.createGitHubClient(auth.token, {
426
+ apiUrl: selection.projectConfig.tracker.apiUrl
427
+ });
428
+ let detail;
429
+ try {
430
+ detail = await workflowCommandDependencies.getGitHubProjectDetail(
431
+ client,
432
+ githubProjectId
433
+ );
434
+ } catch (error) {
435
+ const message = error instanceof GitHubApiError ? error.message : error instanceof Error ? error.message : "Unknown GitHub API error.";
436
+ throw new Error(
437
+ `Failed to resolve the configured GitHub Project binding '${githubProjectId}': ${message}`
438
+ );
439
+ }
440
+ if (!findLinkedRepository(detail, issue.owner, issue.name)) {
441
+ throw new Error(
442
+ `Repository ${issue.owner}/${issue.name} is not linked to the configured GitHub Project "${detail.title}". Run 'gh-symphony repo add ${issue.owner}/${issue.name}' or re-run 'gh-symphony project add' with the correct project binding.`
443
+ );
444
+ }
445
+ const trackedIssue = await workflowCommandDependencies.fetchLiveIssue(
446
+ {
447
+ projectId: githubProjectId,
448
+ token: auth.token,
449
+ apiUrl: selection.projectConfig.tracker.apiUrl,
450
+ assignedOnly: selection.projectConfig.tracker.settings?.assignedOnly === true,
451
+ priorityFieldName: typeof selection.projectConfig.tracker.settings?.priorityFieldName === "string" ? selection.projectConfig.tracker.settings.priorityFieldName : void 0,
452
+ timeoutMs: typeof selection.projectConfig.tracker.settings?.timeoutMs === "number" ? selection.projectConfig.tracker.settings.timeoutMs : void 0
453
+ },
454
+ {
455
+ owner: issue.owner,
456
+ name: issue.name
457
+ },
458
+ issue.number
459
+ );
460
+ if (!trackedIssue) {
461
+ throw new Error(
462
+ `Issue ${issue.identifier} is not in the configured GitHub Project "${detail.title}". Add the issue to the project and re-run the preview.`
463
+ );
464
+ }
465
+ return {
466
+ issue: trackedIssue,
467
+ sampleSource: `live:${trackedIssue.identifier}`
468
+ };
469
+ }
316
470
  function validateWorkflow(workflowPath, markdown) {
317
471
  const workflow = parseWorkflowMarkdown(markdown);
318
472
  const promptFreshVariables = buildPromptVariables(SAMPLE_ISSUE, {
@@ -420,9 +574,19 @@ async function runValidate(args, options) {
420
574
  }
421
575
  async function runPreview(args, options) {
422
576
  const flags = parsePreviewFlags(args);
577
+ if (flags.sample && flags.issue) {
578
+ throw new Error(
579
+ "Options '--sample' and '--issue' cannot be used together."
580
+ );
581
+ }
423
582
  const { workflowPath, markdown } = await loadWorkflowMarkdown(flags.file);
424
583
  const workflow = parseWorkflowMarkdown(markdown);
425
- const { issue, sampleSource } = await loadSampleIssue(flags.sample);
584
+ if (flags.issue && workflow.tracker.kind !== "github-project") {
585
+ throw new Error(
586
+ "Live issue preview requires 'tracker.kind: github-project' in WORKFLOW.md."
587
+ );
588
+ }
589
+ const { issue, sampleSource } = flags.issue ? await loadLiveIssue(flags.issue, flags.projectId, options) : await loadSampleIssue(flags.sample);
426
590
  const variables = buildPromptVariables(issue, {
427
591
  attempt: flags.attempt
428
592
  });
@@ -493,5 +657,7 @@ var handler = async (args, options) => {
493
657
  };
494
658
  var workflow_default = handler;
495
659
  export {
496
- workflow_default as default
660
+ workflow_default as default,
661
+ resetWorkflowCommandDependenciesForTest,
662
+ setWorkflowCommandDependenciesForTest
497
663
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-symphony/cli",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "license": "MIT",
5
5
  "author": "hojinzs",
6
6
  "description": "Interactive CLI for GitHub Symphony orchestration",
@@ -41,11 +41,13 @@
41
41
  },
42
42
  "devDependencies": {
43
43
  "tsup": "^8.5.1",
44
+ "@gh-symphony/control-plane": "0.0.14",
44
45
  "@gh-symphony/core": "0.0.14",
45
46
  "@gh-symphony/orchestrator": "0.0.14",
46
47
  "@gh-symphony/dashboard": "0.0.14",
47
- "@gh-symphony/worker": "0.0.14",
48
- "@gh-symphony/tracker-github": "0.0.14"
48
+ "@gh-symphony/runtime-claude": "0.0.14",
49
+ "@gh-symphony/tracker-github": "0.0.14",
50
+ "@gh-symphony/worker": "0.0.14"
49
51
  },
50
52
  "scripts": {
51
53
  "build": "tsup",