@gh-symphony/cli 0.2.5 → 0.3.0

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.
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-RZ3WO7OV.js";
5
5
  import {
6
6
  parseWorkflowMarkdown
7
- } from "./chunk-3SKN5L3I.js";
7
+ } from "./chunk-5HQLPZR5.js";
8
8
  import {
9
9
  saveGlobalConfig,
10
10
  saveProjectConfig
@@ -5,7 +5,8 @@ var DEFAULT_WORKFLOW_LIFECYCLE = {
5
5
  stateFieldName: "Status",
6
6
  activeStates: ["Todo", "In Progress"],
7
7
  terminalStates: ["Done"],
8
- blockerCheckStates: ["Todo"]
8
+ blockerCheckStates: [],
9
+ planningStates: []
9
10
  };
10
11
  function isStateActive(state, lifecycle) {
11
12
  return matchesWorkflowState(state, lifecycle.activeStates);
@@ -15,7 +16,9 @@ function isStateTerminal(state, lifecycle) {
15
16
  }
16
17
  function matchesWorkflowState(state, candidates) {
17
18
  const normalizedState = normalizeWorkflowState(state);
18
- return candidates.some((candidate) => normalizeWorkflowState(candidate) === normalizedState);
19
+ return candidates.some(
20
+ (candidate) => normalizeWorkflowState(candidate) === normalizedState
21
+ );
19
22
  }
20
23
  function normalizeWorkflowState(state) {
21
24
  return state.trim().toLowerCase();
@@ -55,7 +58,8 @@ var DEFAULT_WORKFLOW_TRACKER = {
55
58
  stateFieldName: DEFAULT_WORKFLOW_LIFECYCLE.stateFieldName,
56
59
  priority: null,
57
60
  priorityFieldName: null,
58
- blockerCheckStates: DEFAULT_WORKFLOW_LIFECYCLE.blockerCheckStates
61
+ blockerCheckStates: DEFAULT_WORKFLOW_LIFECYCLE.blockerCheckStates,
62
+ planningStates: DEFAULT_WORKFLOW_LIFECYCLE.planningStates
59
63
  };
60
64
  var DEFAULT_WORKFLOW_WORKSPACE = {
61
65
  root: null
@@ -135,6 +139,7 @@ function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
135
139
  const activeStates = readStringList(tracker, "active_states") ?? DEFAULT_WORKFLOW_TRACKER.activeStates;
136
140
  const terminalStates = readStringList(tracker, "terminal_states") ?? DEFAULT_WORKFLOW_TRACKER.terminalStates;
137
141
  const blockerCheckStates = readStringList(tracker, "blocker_check_states") ?? DEFAULT_WORKFLOW_TRACKER.blockerCheckStates;
142
+ const planningStates = readStringList(tracker, "planning_states") ?? blockerCheckStates;
138
143
  const maxConcurrentAgentsByState = readNumberMap(
139
144
  agent,
140
145
  "max_concurrent_agents_by_state"
@@ -172,7 +177,8 @@ function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
172
177
  stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
173
178
  priority: readPriorityConfig(tracker, env),
174
179
  priorityFieldName: readOptionalString(tracker, "priority_field", env),
175
- blockerCheckStates
180
+ blockerCheckStates,
181
+ planningStates
176
182
  },
177
183
  polling: {
178
184
  intervalMs: readOptionalIntegerLike(polling, "interval_ms") ?? DEFAULT_POLL_INTERVAL_MS
@@ -201,7 +207,8 @@ function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
201
207
  stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
202
208
  activeStates,
203
209
  terminalStates,
204
- blockerCheckStates
210
+ blockerCheckStates,
211
+ planningStates
205
212
  },
206
213
  format: "front-matter",
207
214
  githubProjectId: readOptionalString(tracker, "project_id", env),
@@ -451,7 +458,9 @@ function parseScalar(value) {
451
458
  return parsed;
452
459
  }
453
460
  } catch {
454
- throw new Error(`Invalid quoted workflow front matter scalar "${value}".`);
461
+ throw new Error(
462
+ `Invalid quoted workflow front matter scalar "${value}".`
463
+ );
455
464
  }
456
465
  }
457
466
  if (value.startsWith("'") && value.endsWith("'")) {
@@ -10,7 +10,7 @@ import {
10
10
  resolveGitHubGraphQLToken,
11
11
  shouldReuseAgentCredentialCache,
12
12
  writeAgentCredentialCache
13
- } from "./chunk-3SKN5L3I.js";
13
+ } from "./chunk-5HQLPZR5.js";
14
14
 
15
15
  // ../runtime-codex/src/runtime.ts
16
16
  import { spawn } from "child_process";
@@ -16,7 +16,7 @@ import {
16
16
  formatClaudePreflightText,
17
17
  resolveClaudeCommandBinary,
18
18
  runClaudePreflight
19
- } from "./chunk-3SKN5L3I.js";
19
+ } from "./chunk-5HQLPZR5.js";
20
20
 
21
21
  // src/mapping/smart-defaults.ts
22
22
  var ROLE_PATTERNS = [
@@ -45,10 +45,9 @@ function inferStateRole(columnName) {
45
45
  function inferAllStateRoles(columnNames) {
46
46
  return columnNames.map(inferStateRole);
47
47
  }
48
- function toWorkflowLifecycleConfig(stateFieldName, mappings) {
48
+ function toWorkflowLifecycleConfig(stateFieldName, mappings, options = {}) {
49
49
  const activeStates = [];
50
50
  const terminalStates = [];
51
- const blockerCheckStates = [];
52
51
  for (const [columnName, mapping] of Object.entries(mappings)) {
53
52
  switch (mapping.role) {
54
53
  case "active":
@@ -61,14 +60,13 @@ function toWorkflowLifecycleConfig(stateFieldName, mappings) {
61
60
  break;
62
61
  }
63
62
  }
64
- if (activeStates.length > 0) {
65
- blockerCheckStates.push(activeStates[0]);
66
- }
63
+ const blockerCheckStates = options.blockerCheckStates ?? [];
67
64
  return {
68
65
  stateFieldName,
69
66
  activeStates,
70
67
  terminalStates,
71
- blockerCheckStates
68
+ blockerCheckStates,
69
+ planningStates: options.planningStates ?? blockerCheckStates
72
70
  };
73
71
  }
74
72
  function validateStateMapping(mappings) {
@@ -361,12 +359,18 @@ function buildFrontMatter(input) {
361
359
  lines.push(` - ${state}`);
362
360
  }
363
361
  }
364
- if (input.lifecycle.blockerCheckStates.length > 0) {
365
- lines.push(" blocker_check_states:");
366
- for (const state of input.lifecycle.blockerCheckStates) {
367
- lines.push(` - ${state}`);
368
- }
369
- }
362
+ lines.push(
363
+ ...buildStringListFrontMatter(
364
+ "blocker_check_states",
365
+ input.lifecycle.blockerCheckStates
366
+ )
367
+ );
368
+ lines.push(
369
+ ...buildStringListFrontMatter(
370
+ "planning_states",
371
+ input.lifecycle.planningStates
372
+ )
373
+ );
370
374
  lines.push("polling:");
371
375
  lines.push(` interval_ms: ${input.pollIntervalMs ?? 3e4}`);
372
376
  lines.push("workspace:");
@@ -380,6 +384,12 @@ function buildFrontMatter(input) {
380
384
  lines.push(...buildRuntimeFrontMatter(input.runtime));
381
385
  return lines.join("\n") + "\n";
382
386
  }
387
+ function buildStringListFrontMatter(key, values) {
388
+ if (values.length === 0) {
389
+ return [` ${key}: []`];
390
+ }
391
+ return [` ${key}:`, ...values.map((value) => ` - ${value}`)];
392
+ }
383
393
  function buildPriorityFrontMatter(input) {
384
394
  const lines = [];
385
395
  if (!input.priority) {
@@ -984,7 +994,8 @@ function generateReferenceWorkflow(input) {
984
994
  const terminalColumns = input.statusColumns.filter(
985
995
  (c) => c.role === "terminal"
986
996
  );
987
- const firstActive = activeColumns[0];
997
+ const blockerCheckStates = input.lifecycle?.blockerCheckStates ?? [];
998
+ const planningStates = input.lifecycle?.planningStates ?? blockerCheckStates;
988
999
  if (activeColumns.length > 0) {
989
1000
  lines.push(" active_states:");
990
1001
  for (const col of activeColumns) {
@@ -1001,12 +1012,10 @@ function generateReferenceWorkflow(input) {
1001
1012
  } else {
1002
1013
  lines.push(" terminal_states: [{terminal column names}]");
1003
1014
  }
1004
- if (firstActive) {
1005
- lines.push(" blocker_check_states:");
1006
- lines.push(` - ${firstActive.name}`);
1007
- } else {
1008
- lines.push(" blocker_check_states: [{first active state}]");
1009
- }
1015
+ lines.push(
1016
+ ...buildReferenceStringList("blocker_check_states", blockerCheckStates)
1017
+ );
1018
+ lines.push(...buildReferenceStringList("planning_states", planningStates));
1010
1019
  lines.push("");
1011
1020
  lines.push("# Linear tracker example:");
1012
1021
  lines.push("# tracker:");
@@ -1021,7 +1030,9 @@ function generateReferenceWorkflow(input) {
1021
1030
  lines.push("# - Done");
1022
1031
  lines.push("# - Canceled");
1023
1032
  lines.push("# - Duplicate");
1024
- lines.push("# Linear uses repository-local polling; gh-symphony does not provide");
1033
+ lines.push(
1034
+ "# Linear uses repository-local polling; gh-symphony does not provide"
1035
+ );
1025
1036
  lines.push("# a Linear webhook setup command.");
1026
1037
  lines.push("");
1027
1038
  lines.push("polling:");
@@ -1282,6 +1293,12 @@ function generateReferenceWorkflow(input) {
1282
1293
  lines.push("");
1283
1294
  return lines.join("\n");
1284
1295
  }
1296
+ function buildReferenceStringList(key, values) {
1297
+ if (values.length === 0) {
1298
+ return [` ${key}: []`];
1299
+ }
1300
+ return [` ${key}:`, ...values.map((value) => ` - ${value}`)];
1301
+ }
1285
1302
  function buildReferencePriorityLines(priority) {
1286
1303
  const lines = [];
1287
1304
  if (priority?.source === "project-field" || priority?.source === "labels") {
@@ -2017,7 +2034,7 @@ function validateInitRuntime(runtime) {
2017
2034
  async function promptRuntimeSelection() {
2018
2035
  return abortIfCancelled(
2019
2036
  p.select({
2020
- message: "Step 1/3 \u2014 Select the agent runtime:",
2037
+ message: "Step 1/5 \u2014 Select the agent runtime:",
2021
2038
  options: [
2022
2039
  {
2023
2040
  value: "codex-app-server",
@@ -2318,16 +2335,68 @@ async function promptStateMappings(statusField, options) {
2318
2335
  }
2319
2336
  return mappings;
2320
2337
  }
2338
+ function getDefaultBlockerCheckStates(lifecycle) {
2339
+ const firstActive = lifecycle.activeStates[0];
2340
+ return firstActive ? [firstActive] : [];
2341
+ }
2342
+ async function promptBlockerCheck(lifecycle, options) {
2343
+ const stepLabel = options?.stepLabel ?? "Step 3/5";
2344
+ const activeStates = lifecycle.activeStates;
2345
+ const defaultStates = getDefaultBlockerCheckStates(lifecycle);
2346
+ if (activeStates.length === 0) {
2347
+ p.log.warn("No active states; blocker check cannot be enabled.");
2348
+ p.log.info("Blocker check: disabled");
2349
+ return [];
2350
+ }
2351
+ const activeStateSummary = activeStates.length === 1 ? `"${activeStates[0]}"` : "selected active states";
2352
+ const enabled = await abortIfCancelled(
2353
+ p.confirm({
2354
+ message: `${stepLabel} \u2014 Enable blocker check? Issues with unresolved "blocked by" dependencies will be held back from dispatch on ${activeStateSummary}.`,
2355
+ initialValue: true
2356
+ })
2357
+ );
2358
+ if (!enabled) {
2359
+ p.log.info("Blocker check: disabled");
2360
+ return [];
2361
+ }
2362
+ if (activeStates.length === 1) {
2363
+ p.log.info(`Blocker check applies to: ${activeStates[0]}`);
2364
+ return [activeStates[0]];
2365
+ }
2366
+ const selectedStates = await abortIfCancelled(
2367
+ p.multiselect({
2368
+ message: `${stepLabel} \u2014 Which active states should be blocker-checked?`,
2369
+ options: activeStates.map((state) => ({
2370
+ value: state,
2371
+ label: state,
2372
+ hint: defaultStates.includes(state) ? "default" : void 0
2373
+ })),
2374
+ initialValues: defaultStates,
2375
+ required: true
2376
+ })
2377
+ );
2378
+ p.log.info(`Blocker check applies to: ${selectedStates.join(", ")}`);
2379
+ return [...selectedStates];
2380
+ }
2321
2381
  async function planWorkflowArtifacts(opts) {
2322
2382
  const environment = opts.environment ?? await detectEnvironment(opts.cwd);
2323
2383
  const priority = opts.priority ?? (opts.priorityField ? buildProjectFieldPriority(opts.priorityField) : buildDisabledPriority());
2384
+ const defaultLifecycle = toWorkflowLifecycleConfig(
2385
+ opts.statusField.name,
2386
+ opts.mappings
2387
+ );
2388
+ const defaultBlockerCheckStates = getDefaultBlockerCheckStates(defaultLifecycle);
2389
+ const lifecycle = opts.lifecycle ?? toWorkflowLifecycleConfig(opts.statusField.name, opts.mappings, {
2390
+ blockerCheckStates: defaultBlockerCheckStates,
2391
+ planningStates: defaultBlockerCheckStates
2392
+ });
2324
2393
  const workflowMd = generateWorkflowMarkdown({
2325
2394
  projectId: opts.projectDetail.id,
2326
2395
  stateFieldName: opts.statusField.name,
2327
2396
  priority,
2328
2397
  includePriorityTemplates: opts.includePriorityTemplates ?? priority.source === "disabled",
2329
2398
  mappings: opts.mappings,
2330
- lifecycle: toWorkflowLifecycleConfig(opts.statusField.name, opts.mappings),
2399
+ lifecycle,
2331
2400
  runtime: opts.runtime,
2332
2401
  detectedEnvironment: environment
2333
2402
  });
@@ -2343,6 +2412,7 @@ async function planWorkflowArtifacts(opts) {
2343
2412
  statusField: opts.statusField,
2344
2413
  priorityField: opts.priorityField,
2345
2414
  priority,
2415
+ lifecycle,
2346
2416
  includePriorityTemplates: opts.includePriorityTemplates ?? priority.source === "disabled",
2347
2417
  runtime: opts.runtime,
2348
2418
  skipSkills: opts.skipSkills,
@@ -2368,6 +2438,11 @@ function summarizeEnvironment(env) {
2368
2438
  `Existing skills ${env.existingSkills.length === 0 ? "none" : env.existingSkills.join(", ")}`
2369
2439
  ];
2370
2440
  }
2441
+ function deriveWaitStates(statusField, lifecycle) {
2442
+ const active = new Set(lifecycle.activeStates);
2443
+ const terminal = new Set(lifecycle.terminalStates);
2444
+ return statusField.options.map((option) => option.name).filter((state) => !active.has(state) && !terminal.has(state));
2445
+ }
2371
2446
  async function planEcosystem(opts) {
2372
2447
  const {
2373
2448
  cwd,
@@ -2379,6 +2454,19 @@ async function planEcosystem(opts) {
2379
2454
  skipContext
2380
2455
  } = opts;
2381
2456
  const priority = opts.priority ?? (priorityField ? buildProjectFieldPriority(priorityField) : buildDisabledPriority());
2457
+ const automaticLifecycle = toWorkflowLifecycleConfig(
2458
+ statusField.name,
2459
+ buildAutomaticStateMappings(statusField)
2460
+ );
2461
+ const defaultBlockerCheckStates = getDefaultBlockerCheckStates(automaticLifecycle);
2462
+ const lifecycle = opts.lifecycle ?? toWorkflowLifecycleConfig(
2463
+ statusField.name,
2464
+ buildAutomaticStateMappings(statusField),
2465
+ {
2466
+ blockerCheckStates: defaultBlockerCheckStates,
2467
+ planningStates: defaultBlockerCheckStates
2468
+ }
2469
+ );
2382
2470
  const ghSymphonyDir = join3(cwd, ".gh-symphony");
2383
2471
  const environment = opts.environment ?? await detectEnvironment(cwd);
2384
2472
  const files = [];
@@ -2418,6 +2506,7 @@ async function planEcosystem(opts) {
2418
2506
  })),
2419
2507
  projectId: projectDetail.id,
2420
2508
  priority,
2509
+ lifecycle,
2421
2510
  detectedEnvironment: environment
2422
2511
  });
2423
2512
  files.push(
@@ -2469,6 +2558,8 @@ async function planEcosystem(opts) {
2469
2558
  githubProjectTitle: projectDetail.title,
2470
2559
  runtime,
2471
2560
  priority,
2561
+ lifecycle,
2562
+ waitStates: deriveWaitStates(statusField, lifecycle),
2472
2563
  skillsDir,
2473
2564
  skipSkills,
2474
2565
  environment,
@@ -2518,6 +2609,8 @@ async function writeEcosystem(opts) {
2518
2609
  githubProjectTitle: plan.githubProjectTitle,
2519
2610
  runtime: plan.runtime,
2520
2611
  priority: plan.priority,
2612
+ lifecycle: plan.lifecycle,
2613
+ waitStates: plan.waitStates,
2521
2614
  skillsDir: plan.skillsDir,
2522
2615
  skipSkills: plan.skipSkills,
2523
2616
  afterCreateHookWritten,
@@ -2539,9 +2632,20 @@ function formatPrioritySummaryLines(priority) {
2539
2632
  ];
2540
2633
  }
2541
2634
  const mapping = Object.entries(priority.labels).map(([name, value]) => `${name}=${value}`).join(", ");
2635
+ return ["Priority source labels", `Priority mapping ${mapping || "none"}`];
2636
+ }
2637
+ function formatLifecycleValue(states) {
2638
+ return states.length > 0 ? states.join(", ") : "disabled";
2639
+ }
2640
+ function formatLifecycleSummaryLines(lifecycle, waitStates) {
2542
2641
  return [
2543
- "Priority source labels",
2544
- `Priority mapping ${mapping || "none"}`
2642
+ "Lifecycle",
2643
+ ` Status field ${lifecycle.stateFieldName}`,
2644
+ ` Active ${lifecycle.activeStates.join(", ") || "(none)"}`,
2645
+ ` Wait ${waitStates.join(", ") || "(none)"}`,
2646
+ ` Terminal ${lifecycle.terminalStates.join(", ") || "(none)"}`,
2647
+ ` Blocker check ${formatLifecycleValue(lifecycle.blockerCheckStates)}`,
2648
+ ` Planning ${formatLifecycleValue(lifecycle.planningStates)}`
2545
2649
  ];
2546
2650
  }
2547
2651
  function printEcosystemSummary(result, workflowPath, opts) {
@@ -2554,6 +2658,10 @@ function printEcosystemSummary(result, workflowPath, opts) {
2554
2658
  lines.push(`Runtime ${result.runtime}`);
2555
2659
  lines.push(...formatPrioritySummaryLines(result.priority));
2556
2660
  lines.push("");
2661
+ lines.push(
2662
+ ...formatLifecycleSummaryLines(result.lifecycle, result.waitStates)
2663
+ );
2664
+ lines.push("");
2557
2665
  lines.push("Generated files");
2558
2666
  lines.push(` \u2713 WORKFLOW.md ${relWorkflow}`);
2559
2667
  if (result.afterCreateHookWritten) {
@@ -2608,6 +2716,13 @@ function renderDryRunPreview(workflowPath, workflowPlan, ecosystemPlan) {
2608
2716
  lines.push(`Runtime ${ecosystemPlan.runtime}`);
2609
2717
  lines.push(...formatPrioritySummaryLines(ecosystemPlan.priority));
2610
2718
  lines.push("");
2719
+ lines.push(
2720
+ ...formatLifecycleSummaryLines(
2721
+ ecosystemPlan.lifecycle,
2722
+ ecosystemPlan.waitStates
2723
+ )
2724
+ );
2725
+ lines.push("");
2611
2726
  lines.push("Planned file changes");
2612
2727
  lines.push(
2613
2728
  ` ${statusIcon[workflowPlan.status]} ${workflowPlan.status.padEnd(9)} WORKFLOW.md ${relWorkflow}`
@@ -2737,6 +2852,13 @@ Run without --non-interactive for manual mapping.
2737
2852
  process.exitCode = 1;
2738
2853
  return;
2739
2854
  }
2855
+ const defaultBlockerCheckStates = getDefaultBlockerCheckStates(
2856
+ toWorkflowLifecycleConfig(statusField.name, mappings)
2857
+ );
2858
+ const lifecycle = toWorkflowLifecycleConfig(statusField.name, mappings, {
2859
+ blockerCheckStates: defaultBlockerCheckStates,
2860
+ planningStates: defaultBlockerCheckStates
2861
+ });
2740
2862
  const outputPath = resolve(flags.output ?? "WORKFLOW.md");
2741
2863
  const { workflowPlan, ecosystemPlan } = await planWorkflowArtifacts({
2742
2864
  cwd: process.cwd(),
@@ -2747,6 +2869,7 @@ Run without --non-interactive for manual mapping.
2747
2869
  priority,
2748
2870
  includePriorityTemplates: !autoPriorityField,
2749
2871
  mappings,
2872
+ lifecycle,
2750
2873
  runtime,
2751
2874
  skipSkills: flags.skipSkills,
2752
2875
  skipContext: flags.skipContext
@@ -2771,6 +2894,7 @@ Run without --non-interactive for manual mapping.
2771
2894
  priorityField: autoPriorityField,
2772
2895
  priority,
2773
2896
  includePriorityTemplates: !autoPriorityField,
2897
+ lifecycle,
2774
2898
  runtime,
2775
2899
  skipSkills: flags.skipSkills,
2776
2900
  skipContext: flags.skipContext
@@ -2847,7 +2971,7 @@ async function runInteractiveStandalone(flags, _options) {
2847
2971
  }
2848
2972
  const selectedGithubProjectId = await abortIfCancelled(
2849
2973
  p.select({
2850
- message: "Step 2/3 \u2014 Select a GitHub Project board:",
2974
+ message: "Step 2/5 \u2014 Select a GitHub Project board:",
2851
2975
  options: projects.map((proj) => ({
2852
2976
  value: proj.id,
2853
2977
  label: `${proj.owner.login}/${proj.title}`,
@@ -2882,12 +3006,7 @@ async function runInteractiveStandalone(flags, _options) {
2882
3006
  projectDetail.linkedRepositories
2883
3007
  );
2884
3008
  const mappings = await promptStateMappings(statusField, {
2885
- stepLabel: "Step 3/4"
2886
- });
2887
- const { priority, priorityField } = await promptPriorityConfig({
2888
- priorityResolution,
2889
- labelNames: priorityLabelNames,
2890
- stepLabel: "Step 4/4"
3009
+ stepLabel: "Step 3/5"
2891
3010
  });
2892
3011
  const validation = validateStateMapping(mappings);
2893
3012
  if (!validation.valid) {
@@ -2901,6 +3020,19 @@ async function runInteractiveStandalone(flags, _options) {
2901
3020
  for (const warn of validation.warnings) {
2902
3021
  p.log.warn(` \u26A0 ${warn}`);
2903
3022
  }
3023
+ const lifecycleBase = toWorkflowLifecycleConfig(statusField.name, mappings);
3024
+ const blockerCheckStates = await promptBlockerCheck(lifecycleBase, {
3025
+ stepLabel: "Step 4/5"
3026
+ });
3027
+ const lifecycle = toWorkflowLifecycleConfig(statusField.name, mappings, {
3028
+ blockerCheckStates,
3029
+ planningStates: blockerCheckStates
3030
+ });
3031
+ const { priority, priorityField } = await promptPriorityConfig({
3032
+ priorityResolution,
3033
+ labelNames: priorityLabelNames,
3034
+ stepLabel: "Step 5/5"
3035
+ });
2904
3036
  const outputPath = resolve(flags.output ?? "WORKFLOW.md");
2905
3037
  const { workflowPlan, ecosystemPlan } = await planWorkflowArtifacts({
2906
3038
  cwd: process.cwd(),
@@ -2911,6 +3043,7 @@ async function runInteractiveStandalone(flags, _options) {
2911
3043
  priority,
2912
3044
  includePriorityTemplates: priority.source === "disabled",
2913
3045
  mappings,
3046
+ lifecycle,
2914
3047
  runtime,
2915
3048
  skipSkills: flags.skipSkills,
2916
3049
  skipContext: flags.skipContext
@@ -2927,6 +3060,7 @@ async function runInteractiveStandalone(flags, _options) {
2927
3060
  priorityField,
2928
3061
  priority,
2929
3062
  includePriorityTemplates: priority.source === "disabled",
3063
+ lifecycle,
2930
3064
  runtime,
2931
3065
  skipSkills: flags.skipSkills,
2932
3066
  skipContext: flags.skipContext
@@ -2938,6 +3072,7 @@ async function runInteractiveStandalone(flags, _options) {
2938
3072
  }
2939
3073
 
2940
3074
  export {
3075
+ toWorkflowLifecycleConfig,
2941
3076
  validateStateMapping,
2942
3077
  abortIfCancelled,
2943
3078
  workflow_init_default,
@@ -2947,6 +3082,7 @@ export {
2947
3082
  collectPriorityLabelNames,
2948
3083
  promptPriorityConfig,
2949
3084
  promptStateMappings,
3085
+ promptBlockerCheck,
2950
3086
  planWorkflowArtifacts,
2951
3087
  writeWorkflowPlan,
2952
3088
  writeEcosystem,
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  workflow_init_default
4
- } from "./chunk-DTPIJO6S.js";
4
+ } from "./chunk-GL55AKBI.js";
5
5
  import {
6
6
  fetchGithubProjectIssueByRepositoryAndNumber,
7
7
  inspectManagedProjectSelection,
8
8
  resolveTrackerAdapter
9
- } from "./chunk-NRABQNAX.js";
9
+ } from "./chunk-VHEGRYK7.js";
10
10
  import {
11
11
  GitHubApiError,
12
12
  createClient,
@@ -19,7 +19,7 @@ import {
19
19
  buildPromptVariables,
20
20
  parseWorkflowMarkdown,
21
21
  renderPrompt
22
- } from "./chunk-3SKN5L3I.js";
22
+ } from "./chunk-5HQLPZR5.js";
23
23
  import {
24
24
  loadActiveProjectConfig
25
25
  } from "./chunk-4ICDSQCJ.js";
@@ -705,7 +705,9 @@ async function loadLinearIssue(issueIdentifier, workflow, options) {
705
705
  repository: projectConfig.repository,
706
706
  tracker: projectConfig.tracker
707
707
  };
708
- const trackerAdapter = workflowCommandDependencies.resolveTrackerAdapter(projectConfig.tracker);
708
+ const trackerAdapter = workflowCommandDependencies.resolveTrackerAdapter(
709
+ projectConfig.tracker
710
+ );
709
711
  const [issue] = await trackerAdapter.fetchIssueStatesByIds(
710
712
  orchestratorProject,
711
713
  [issueIdentifier.trim().toUpperCase()],
@@ -752,6 +754,7 @@ function validateWorkflow(workflowPath, markdown) {
752
754
  activeStates: workflow.lifecycle.activeStates,
753
755
  terminalStates: workflow.lifecycle.terminalStates,
754
756
  blockerCheckStates: workflow.lifecycle.blockerCheckStates,
757
+ planningStates: workflow.lifecycle.planningStates,
755
758
  pollingIntervalMs: workflow.polling.intervalMs,
756
759
  workspaceRoot: workflow.workspace.root,
757
760
  agentCommand: workflow.agentCommand,
@@ -791,6 +794,7 @@ Lifecycle
791
794
  active_states=${report.summary.activeStates.join(", ") || "(none)"}
792
795
  terminal_states=${report.summary.terminalStates.join(", ") || "(none)"}
793
796
  blocker_check_states=${report.summary.blockerCheckStates.join(", ") || "(none)"}
797
+ planning_states=${report.summary.planningStates.join(", ") || "(none)"}
794
798
 
795
799
  Runtime
796
800
  polling.interval_ms=${report.summary.pollingIntervalMs}
@@ -30,7 +30,7 @@ import {
30
30
  resolveWorkflowRuntimeTimeouts,
31
31
  safeReadDir,
32
32
  scheduleRetryAt
33
- } from "./chunk-3SKN5L3I.js";
33
+ } from "./chunk-5HQLPZR5.js";
34
34
  import {
35
35
  loadGlobalConfig,
36
36
  loadProjectConfig
@@ -4156,7 +4156,8 @@ var OrchestratorService = class {
4156
4156
  stateFieldName: "Status",
4157
4157
  activeStates: ["Todo", "In Progress"],
4158
4158
  terminalStates: ["Done"],
4159
- blockerCheckStates: ["Todo"]
4159
+ blockerCheckStates: ["Todo"],
4160
+ planningStates: ["Todo"]
4160
4161
  }
4161
4162
  };
4162
4163
  }
@@ -5,14 +5,14 @@ import {
5
5
  parseIssueReference,
6
6
  readGitHubProjectBinding,
7
7
  renderIssueWorkflowPreview
8
- } from "./chunk-5U36B7FC.js";
9
- import "./chunk-DTPIJO6S.js";
8
+ } from "./chunk-GTNWFVFU.js";
9
+ import "./chunk-GL55AKBI.js";
10
10
  import {
11
11
  fetchGithubProjectIssueByRepositoryAndNumber,
12
12
  fetchGithubProjectIssues,
13
13
  inspectManagedProjectSelection
14
- } from "./chunk-NRABQNAX.js";
15
- import "./chunk-FAU72YC2.js";
14
+ } from "./chunk-VHEGRYK7.js";
15
+ import "./chunk-A7EFL6EJ.js";
16
16
  import {
17
17
  resolveRuntimeRoot
18
18
  } from "./chunk-RZ3WO7OV.js";
@@ -40,7 +40,7 @@ import {
40
40
  resolveClaudeCommandBinary,
41
41
  resolveRuntimeCommandBinary,
42
42
  runClaudePreflight
43
- } from "./chunk-3SKN5L3I.js";
43
+ } from "./chunk-5HQLPZR5.js";
44
44
  import {
45
45
  configFilePath,
46
46
  orchestratorLogPath,
package/dist/index.js CHANGED
@@ -417,13 +417,13 @@ function createRemovedCommandHandler(message) {
417
417
 
418
418
  // src/index.ts
419
419
  var COMMANDS = {
420
- workflow: () => import("./workflow-AV676KAP.js"),
421
- setup: () => import("./setup-T2QENR26.js"),
422
- doctor: () => import("./doctor-TQR54KNZ.js"),
423
- upgrade: () => import("./upgrade-7452LZXX.js"),
424
- repo: () => import("./repo-Y6EF2DZP.js"),
420
+ workflow: () => import("./workflow-4OFPSVZ3.js"),
421
+ setup: () => import("./setup-G5VYN6FV.js"),
422
+ doctor: () => import("./doctor-3IIM4UYS.js"),
423
+ upgrade: () => import("./upgrade-BXIHAENU.js"),
424
+ repo: () => import("./repo-SLK4DDXH.js"),
425
425
  config: () => import("./config-cmd-AOZVS6GU.js"),
426
- version: () => import("./version-D3FB3PXO.js")
426
+ version: () => import("./version-MC2KSPJB.js")
427
427
  };
428
428
  function addGlobalOptions(command) {
429
429
  return command.option("--config <dir>", "Config directory").addOption(new Option("--config-dir <dir>").hideHelp()).option("-v, --verbose", "Enable verbose output").option("--json", "Output in JSON format").option("--no-color", "Disable color output");
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  initRepoRuntime,
19
19
  parseRepoRuntimeFlags
20
- } from "./chunk-DLZ2XHWY.js";
20
+ } from "./chunk-2ZIPQ7ZS.js";
21
21
  import {
22
22
  OrchestratorService,
23
23
  acquireProjectLock,
@@ -33,8 +33,8 @@ import {
33
33
  resolveOrchestratorLogLevel,
34
34
  resolveTrackerAdapter,
35
35
  runCli
36
- } from "./chunk-NRABQNAX.js";
37
- import "./chunk-FAU72YC2.js";
36
+ } from "./chunk-VHEGRYK7.js";
37
+ import "./chunk-A7EFL6EJ.js";
38
38
  import {
39
39
  resolveRepoRuntimeRoot,
40
40
  resolveRuntimeRoot
@@ -58,7 +58,7 @@ import {
58
58
  parseRecentEvents,
59
59
  readJsonFile,
60
60
  safeReadDir
61
- } from "./chunk-3SKN5L3I.js";
61
+ } from "./chunk-5HQLPZR5.js";
62
62
  import {
63
63
  daemonPidPath,
64
64
  httpStatusPath,
@@ -4,18 +4,20 @@ import {
4
4
  buildAutomaticStateMappings,
5
5
  collectPriorityLabelNames,
6
6
  planWorkflowArtifacts,
7
+ promptBlockerCheck,
7
8
  promptPriorityConfig,
8
9
  promptStateMappings,
9
10
  renderDryRunPreview,
10
11
  resolvePriorityField,
11
12
  resolveStatusField,
13
+ toWorkflowLifecycleConfig,
12
14
  validateStateMapping,
13
15
  writeEcosystem,
14
16
  writeWorkflowPlan
15
- } from "./chunk-DTPIJO6S.js";
17
+ } from "./chunk-GL55AKBI.js";
16
18
  import {
17
19
  initRepoRuntime
18
- } from "./chunk-DLZ2XHWY.js";
20
+ } from "./chunk-2ZIPQ7ZS.js";
19
21
  import "./chunk-RZ3WO7OV.js";
20
22
  import {
21
23
  GhAuthError,
@@ -29,7 +31,7 @@ import {
29
31
  listUserProjects,
30
32
  validateToken
31
33
  } from "./chunk-SMNIGNS3.js";
32
- import "./chunk-3SKN5L3I.js";
34
+ import "./chunk-5HQLPZR5.js";
33
35
  import "./chunk-4ICDSQCJ.js";
34
36
 
35
37
  // src/commands/setup.ts
@@ -106,7 +108,7 @@ async function selectProjectSummary(client) {
106
108
  }
107
109
  const selectedProjectId = await abortIfCancelled(
108
110
  p.select({
109
- message: "Step 1/3 \u2014 Select a GitHub Project board:",
111
+ message: "Step 1/4 \u2014 Select a GitHub Project board:",
110
112
  options: projects.map((project) => ({
111
113
  value: project.id,
112
114
  label: `${project.owner.login}/${project.title}`,
@@ -345,7 +347,7 @@ async function runInteractive(flags, _options) {
345
347
  projectDetail.linkedRepositories
346
348
  );
347
349
  const mappings = await promptStateMappings(statusField, {
348
- stepLabel: "Step 2/3"
350
+ stepLabel: "Step 2/4"
349
351
  });
350
352
  const workflowValidation = validateStateMapping(mappings);
351
353
  if (!workflowValidation.valid) {
@@ -359,10 +361,18 @@ async function runInteractive(flags, _options) {
359
361
  for (const warning of workflowValidation.warnings) {
360
362
  p.log.warn(` \u26A0 ${warning}`);
361
363
  }
364
+ const lifecycleBase = toWorkflowLifecycleConfig(statusField.name, mappings);
365
+ const blockerCheckStates = await promptBlockerCheck(lifecycleBase, {
366
+ stepLabel: "Step 3/4"
367
+ });
368
+ const lifecycle = toWorkflowLifecycleConfig(statusField.name, mappings, {
369
+ blockerCheckStates,
370
+ planningStates: blockerCheckStates
371
+ });
362
372
  const { priority, priorityField } = await promptPriorityConfig({
363
373
  priorityResolution,
364
374
  labelNames: priorityLabelNames,
365
- stepLabel: "Step 3/3"
375
+ stepLabel: "Step 4/4"
366
376
  });
367
377
  const workflowPath = resolve(flags.output ?? "WORKFLOW.md");
368
378
  const { workflowPlan, ecosystemPlan } = await planWorkflowArtifacts({
@@ -374,6 +384,7 @@ async function runInteractive(flags, _options) {
374
384
  priority,
375
385
  includePriorityTemplates: priority.source === "disabled",
376
386
  mappings,
387
+ lifecycle,
377
388
  runtime: "codex",
378
389
  skipSkills: flags.skipSkills,
379
390
  skipContext: flags.skipContext
@@ -406,6 +417,7 @@ async function runInteractive(flags, _options) {
406
417
  statusField,
407
418
  priorityField,
408
419
  priority,
420
+ lifecycle,
409
421
  includePriorityTemplates: priority.source === "disabled",
410
422
  runtime: "codex",
411
423
  skipSkills: flags.skipSkills,
@@ -16,8 +16,8 @@ function execFileAsync(file, args, execFileImpl = execFileCallback) {
16
16
  });
17
17
  }
18
18
  function resolveCurrentCliVersion() {
19
- if ("0.2.5".length > 0) {
20
- return "0.2.5";
19
+ if ("0.3.0".length > 0) {
20
+ return "0.3.0";
21
21
  }
22
22
  const pkg = JSON.parse(
23
23
  readFileSync(new URL("../../package.json", import.meta.url), "utf8")
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/commands/version.ts
4
4
  var handler = async (_args, options) => {
5
- const version = "0.2.5";
5
+ const version = "0.3.0";
6
6
  if (options.json) {
7
7
  process.stdout.write(JSON.stringify({ version }) + "\n");
8
8
  } else {
@@ -6,7 +6,7 @@ import {
6
6
  normalizeCodexRuntimeEvents,
7
7
  prepareCodexRuntimePlan,
8
8
  resolveLocalRuntimeLaunchConfig
9
- } from "./chunk-FAU72YC2.js";
9
+ } from "./chunk-A7EFL6EJ.js";
10
10
  import {
11
11
  DEFAULT_AGENT_INPUT_REQUIRED_REASON,
12
12
  classifySessionExit,
@@ -17,7 +17,7 @@ import {
17
17
  resolveClaudeCommandBinary,
18
18
  resolveWorkflowRuntimeCommand,
19
19
  runClaudePreflight
20
- } from "./chunk-3SKN5L3I.js";
20
+ } from "./chunk-5HQLPZR5.js";
21
21
 
22
22
  // ../worker/src/index.ts
23
23
  import { spawn as spawn2 } from "child_process";
@@ -26,11 +26,11 @@ import { join as join2 } from "path";
26
26
 
27
27
  // ../worker/src/execution-phase.ts
28
28
  function resolveInitialExecutionPhase(input) {
29
- const { issueState, blockerCheckStates, activeStates } = input;
29
+ const { issueState, planningStates, activeStates } = input;
30
30
  if (!issueState) {
31
31
  return null;
32
32
  }
33
- if (blockerCheckStates.includes(issueState)) {
33
+ if (planningStates.includes(issueState)) {
34
34
  return "planning";
35
35
  }
36
36
  if (activeStates.includes(issueState)) {
@@ -788,7 +788,7 @@ async function startAssignedRun() {
788
788
  }
789
789
  runtimeState.executionPhase = resolveInitialExecutionPhase({
790
790
  issueState: runtimeState.run?.state,
791
- blockerCheckStates: workflow.lifecycle.blockerCheckStates,
791
+ planningStates: workflow.lifecycle.planningStates,
792
792
  activeStates: workflow.lifecycle.activeStates
793
793
  });
794
794
  runtimeState.runPhase = "launching_agent";
@@ -6,12 +6,12 @@ import {
6
6
  resetWorkflowCommandDependenciesForTest,
7
7
  setWorkflowCommandDependenciesForTest,
8
8
  workflow_default
9
- } from "./chunk-5U36B7FC.js";
10
- import "./chunk-DTPIJO6S.js";
11
- import "./chunk-NRABQNAX.js";
12
- import "./chunk-FAU72YC2.js";
9
+ } from "./chunk-GTNWFVFU.js";
10
+ import "./chunk-GL55AKBI.js";
11
+ import "./chunk-VHEGRYK7.js";
12
+ import "./chunk-A7EFL6EJ.js";
13
13
  import "./chunk-SMNIGNS3.js";
14
- import "./chunk-3SKN5L3I.js";
14
+ import "./chunk-5HQLPZR5.js";
15
15
  import "./chunk-4ICDSQCJ.js";
16
16
  export {
17
17
  workflow_default as default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-symphony/cli",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "author": "hojinzs",
6
6
  "description": "Interactive CLI for GitHub Symphony orchestration",
@@ -42,12 +42,12 @@
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
45
  "@gh-symphony/control-plane": "0.0.15",
47
- "@gh-symphony/orchestrator": "0.0.14",
46
+ "@gh-symphony/dashboard": "0.0.14",
48
47
  "@gh-symphony/runtime-claude": "0.0.14",
49
- "@gh-symphony/tracker-github": "0.0.14",
50
- "@gh-symphony/worker": "0.0.14"
48
+ "@gh-symphony/orchestrator": "0.0.14",
49
+ "@gh-symphony/worker": "0.0.14",
50
+ "@gh-symphony/tracker-github": "0.0.14"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsup",