@gh-symphony/cli 0.1.4 → 0.2.2

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.
@@ -2,7 +2,9 @@
2
2
  import {
3
3
  abortIfCancelled,
4
4
  buildAutomaticStateMappings,
5
+ collectPriorityLabelNames,
5
6
  planWorkflowArtifacts,
7
+ promptPriorityConfig,
6
8
  promptStateMappings,
7
9
  renderDryRunPreview,
8
10
  resolvePriorityField,
@@ -10,11 +12,11 @@ import {
10
12
  validateStateMapping,
11
13
  writeEcosystem,
12
14
  writeWorkflowPlan
13
- } from "./chunk-RHLUIMBN.js";
15
+ } from "./chunk-PLBG7TZA.js";
14
16
  import {
15
17
  initRepoRuntime
16
- } from "./chunk-DW63WPRE.js";
17
- import "./chunk-6I753NYO.js";
18
+ } from "./chunk-6PFFGP7S.js";
19
+ import "./chunk-RZ3WO7OV.js";
18
20
  import {
19
21
  GhAuthError,
20
22
  GitHubScopeError,
@@ -25,9 +27,9 @@ import {
25
27
  getProjectDetail,
26
28
  listUserProjects,
27
29
  validateToken
28
- } from "./chunk-Z3NZOPLZ.js";
29
- import "./chunk-EWTMSDCE.js";
30
- import "./chunk-WOVNN5NW.js";
30
+ } from "./chunk-BOM2BYZQ.js";
31
+ import "./chunk-3SKN5L3I.js";
32
+ import "./chunk-4ICDSQCJ.js";
31
33
 
32
34
  // src/commands/setup.ts
33
35
  import * as p from "@clack/prompts";
@@ -198,10 +200,17 @@ async function runNonInteractive(flags, options) {
198
200
  const { field: priorityField, ambiguous: ambiguousPriorityFields } = resolvePriorityField(projectDetail, statusField);
199
201
  if (ambiguousPriorityFields.length > 0) {
200
202
  process.stderr.write(
201
- `Warning: Multiple priority-like single-select fields found (${ambiguousPriorityFields.map((field) => `"${field.name}"`).join(", ")}). Skipping tracker.priority_field in non-interactive mode.
203
+ `Warning: Multiple priority-like single-select fields found (${ambiguousPriorityFields.map((field) => `"${field.name}"`).join(", ")}). Writing disabled priority scaffold in non-interactive mode.
202
204
  `
203
205
  );
204
206
  }
207
+ const priority = priorityField ? {
208
+ source: "project-field",
209
+ field: priorityField.name,
210
+ values: Object.fromEntries(
211
+ priorityField.options.map((option, index) => [option.name, index])
212
+ )
213
+ } : { source: "disabled" };
205
214
  const workflowValidation = validateStateMapping(mappings);
206
215
  if (!workflowValidation.valid) {
207
216
  process.stderr.write(
@@ -219,6 +228,8 @@ Run setup without --non-interactive for manual mapping.
219
228
  projectDetail,
220
229
  statusField,
221
230
  priorityField,
231
+ priority,
232
+ includePriorityTemplates: !priorityField,
222
233
  mappings,
223
234
  runtime: "codex",
224
235
  skipSkills: flags.skipSkills,
@@ -230,6 +241,8 @@ Run setup without --non-interactive for manual mapping.
230
241
  projectDetail,
231
242
  statusField,
232
243
  priorityField,
244
+ priority,
245
+ includePriorityTemplates: !priorityField,
233
246
  runtime: "codex",
234
247
  skipSkills: flags.skipSkills,
235
248
  skipContext: flags.skipContext
@@ -325,8 +338,12 @@ async function runInteractive(flags, _options) {
325
338
  return;
326
339
  }
327
340
  const priorityResolution = resolvePriorityField(projectDetail, statusField);
341
+ const priorityLabelNames = await collectPriorityLabelNames(
342
+ client,
343
+ projectDetail.linkedRepositories
344
+ );
328
345
  const mappings = await promptStateMappings(statusField, {
329
- stepLabel: priorityResolution.ambiguous.length > 0 ? "Step 2/4" : "Step 2/3"
346
+ stepLabel: "Step 2/4"
330
347
  });
331
348
  const workflowValidation = validateStateMapping(mappings);
332
349
  if (!workflowValidation.valid) {
@@ -340,32 +357,14 @@ async function runInteractive(flags, _options) {
340
357
  for (const warning of workflowValidation.warnings) {
341
358
  p.log.warn(` \u26A0 ${warning}`);
342
359
  }
343
- const priorityField = priorityResolution.ambiguous.length > 0 ? await (async () => {
344
- const selectedId = await abortIfCancelled(
345
- p.select({
346
- message: "Step 3/4 \u2014 Multiple GitHub Project priority fields look plausible. Select the one Symphony should use:",
347
- options: [
348
- ...priorityResolution.ambiguous.map((field) => ({
349
- value: field.id,
350
- label: field.name,
351
- hint: `${field.options.length} option${field.options.length === 1 ? "" : "s"}`
352
- })),
353
- {
354
- value: "__skip_priority_field__",
355
- label: "Skip priority-aware dispatch",
356
- hint: "Leave tracker.priority_field unset"
357
- }
358
- ]
359
- })
360
- );
361
- if (selectedId === "__skip_priority_field__") {
362
- return null;
363
- }
364
- return priorityResolution.ambiguous.find((field) => field.id === selectedId) ?? null;
365
- })() : priorityResolution.field;
360
+ const { priority, priorityField } = await promptPriorityConfig({
361
+ priorityResolution,
362
+ labelNames: priorityLabelNames,
363
+ stepLabel: "Step 3/4"
364
+ });
366
365
  const promptAssignedOnly = await abortIfCancelled(
367
366
  p.confirm({
368
- message: `${priorityResolution.ambiguous.length > 0 ? "Step 4/4" : "Step 3/3"} \u2014 Only process issues assigned to the authenticated GitHub user?`,
367
+ message: `${"Step 4/4"} \u2014 Only process issues assigned to the authenticated GitHub user?`,
369
368
  initialValue: flags.assignedOnly ?? false
370
369
  })
371
370
  );
@@ -377,6 +376,8 @@ async function runInteractive(flags, _options) {
377
376
  projectDetail,
378
377
  statusField,
379
378
  priorityField,
379
+ priority,
380
+ includePriorityTemplates: priority.source === "disabled",
380
381
  mappings,
381
382
  runtime: "codex",
382
383
  skipSkills: flags.skipSkills,
@@ -410,6 +411,8 @@ async function runInteractive(flags, _options) {
410
411
  projectDetail,
411
412
  statusField,
412
413
  priorityField,
414
+ priority,
415
+ includePriorityTemplates: priority.source === "disabled",
413
416
  runtime: "codex",
414
417
  skipSkills: flags.skipSkills,
415
418
  skipContext: flags.skipContext
@@ -16,8 +16,8 @@ function execFileAsync(file, args, execFileImpl = execFileCallback) {
16
16
  });
17
17
  }
18
18
  function resolveCurrentCliVersion() {
19
- if ("0.1.4".length > 0) {
20
- return "0.1.4";
19
+ if ("0.2.2".length > 0) {
20
+ return "0.2.2";
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.1.4";
5
+ const version = "0.2.2";
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-E7OCBNB2.js";
9
+ } from "./chunk-FAU72YC2.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-EWTMSDCE.js";
20
+ } from "./chunk-3SKN5L3I.js";
21
21
 
22
22
  // ../worker/src/index.ts
23
23
  import { spawn as spawn2 } from "child_process";
@@ -72,6 +72,7 @@ function resolveMaxNonProductiveTurns(env) {
72
72
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_NONPRODUCTIVE_TURNS;
73
73
  }
74
74
  function captureTurnWorkspaceSnapshot(cwd) {
75
+ const headSha = captureGitHeadSha(cwd);
75
76
  const result = spawnSync(
76
77
  "git",
77
78
  ["status", "--porcelain=v1", "--untracked-files=all"],
@@ -83,40 +84,69 @@ function captureTurnWorkspaceSnapshot(cwd) {
83
84
  if (result.status !== 0) {
84
85
  return {
85
86
  fingerprint: null,
86
- changedFiles: []
87
+ changedFiles: [],
88
+ headSha
87
89
  };
88
90
  }
89
91
  const changedFiles = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean).sort();
90
92
  return {
91
93
  fingerprint: changedFiles.join("\n"),
92
- changedFiles
94
+ changedFiles,
95
+ headSha
93
96
  };
94
97
  }
95
98
  function evaluateTurnProgress(previous, current) {
99
+ const headChanged = (previous.headSha !== null || current.headSha !== null) && previous.headSha !== current.headSha;
100
+ const fingerprintUnchanged = previous.fingerprint !== null && current.fingerprint !== null && previous.fingerprint === current.fingerprint;
96
101
  const normalizedPreviousError = normalizeError(previous.lastError);
97
102
  const normalizedCurrentError = normalizeError(current.lastError);
98
103
  const repeatedError = normalizedPreviousError !== null && normalizedCurrentError !== null && normalizedPreviousError === normalizedCurrentError;
104
+ if (headChanged) {
105
+ return {
106
+ nonProductive: false,
107
+ repeatedPattern: false,
108
+ reason: null,
109
+ headChanged,
110
+ fingerprintUnchanged
111
+ };
112
+ }
99
113
  if (repeatedError) {
100
114
  return {
101
115
  nonProductive: true,
102
116
  repeatedPattern: true,
103
- reason: `repeated error: ${normalizedCurrentError}`
117
+ reason: `repeated error: ${normalizedCurrentError}`,
118
+ headChanged,
119
+ fingerprintUnchanged
104
120
  };
105
121
  }
106
- const unchangedWorkspace = previous.fingerprint !== null && current.fingerprint !== null && previous.fingerprint === current.fingerprint;
107
- if (unchangedWorkspace) {
122
+ if (fingerprintUnchanged) {
108
123
  return {
109
124
  nonProductive: true,
110
125
  repeatedPattern: true,
111
- reason: current.changedFiles.length > 0 ? `workspace diff unchanged (${current.changedFiles.length} tracked change${current.changedFiles.length === 1 ? "" : "s"})` : "workspace unchanged"
126
+ reason: current.changedFiles.length > 0 ? `workspace diff unchanged (${current.changedFiles.length} tracked change${current.changedFiles.length === 1 ? "" : "s"})` : "workspace unchanged",
127
+ headChanged,
128
+ fingerprintUnchanged
112
129
  };
113
130
  }
114
131
  return {
115
132
  nonProductive: false,
116
133
  repeatedPattern: false,
117
- reason: null
134
+ reason: null,
135
+ headChanged,
136
+ fingerprintUnchanged
118
137
  };
119
138
  }
139
+ function captureGitHeadSha(cwd) {
140
+ const result = spawnSync("git", ["rev-parse", "HEAD"], {
141
+ cwd,
142
+ encoding: "utf8"
143
+ });
144
+ if (result.status !== 0) {
145
+ return null;
146
+ }
147
+ const headSha = result.stdout.trim();
148
+ return headSha.length > 0 ? headSha : null;
149
+ }
120
150
  function normalizeError(value) {
121
151
  if (typeof value !== "string") {
122
152
  return null;
@@ -1049,6 +1079,19 @@ function summarizeRuntimeStderr(stderr) {
1049
1079
  }
1050
1080
  return lines.slice(-3).join(" | ").slice(0, 1e3);
1051
1081
  }
1082
+ function formatTurnProgressFingerprint(fingerprint) {
1083
+ if (fingerprint === null) {
1084
+ return { state: "unavailable", length: null, preview: null };
1085
+ }
1086
+ if (fingerprint.length === 0) {
1087
+ return { state: "clean", length: 0, preview: "<clean>" };
1088
+ }
1089
+ return {
1090
+ state: "dirty",
1091
+ length: fingerprint.length,
1092
+ preview: fingerprint.slice(0, 500)
1093
+ };
1094
+ }
1052
1095
  async function exitWorkerStartupFailure(message) {
1053
1096
  runtimeState.status = "failed";
1054
1097
  runtimeState.runPhase = "failed";
@@ -1630,6 +1673,31 @@ async function runCodexClientProtocol(child, plan, env, options) {
1630
1673
  previousTurnProgressSnapshot,
1631
1674
  currentTurnProgressSnapshot
1632
1675
  );
1676
+ const progressLogContext = {
1677
+ reason: turnProgress.reason,
1678
+ nonProductive: turnProgress.nonProductive,
1679
+ repeatedPattern: turnProgress.repeatedPattern,
1680
+ headChanged: turnProgress.headChanged,
1681
+ fingerprintUnchanged: turnProgress.fingerprintUnchanged,
1682
+ previous: {
1683
+ fingerprint: formatTurnProgressFingerprint(
1684
+ previousTurnProgressSnapshot.fingerprint
1685
+ ),
1686
+ changedFilesCount: previousTurnProgressSnapshot.changedFiles.length,
1687
+ headSha: previousTurnProgressSnapshot.headSha
1688
+ },
1689
+ current: {
1690
+ fingerprint: formatTurnProgressFingerprint(
1691
+ currentTurnProgressSnapshot.fingerprint
1692
+ ),
1693
+ changedFilesCount: currentTurnProgressSnapshot.changedFiles.length,
1694
+ headSha: currentTurnProgressSnapshot.headSha
1695
+ }
1696
+ };
1697
+ process.stderr.write(
1698
+ `[worker] turn progress evaluation ${JSON.stringify(progressLogContext)}
1699
+ `
1700
+ );
1633
1701
  previousTurnProgressSnapshot = currentTurnProgressSnapshot;
1634
1702
  if (turnProgress.nonProductive) {
1635
1703
  consecutiveNonProductiveTurns += 1;
@@ -6,12 +6,13 @@ import {
6
6
  resetWorkflowCommandDependenciesForTest,
7
7
  setWorkflowCommandDependenciesForTest,
8
8
  workflow_default
9
- } from "./chunk-HT3FAJAO.js";
10
- import "./chunk-RHLUIMBN.js";
11
- import "./chunk-YIARPBOR.js";
12
- import "./chunk-Z3NZOPLZ.js";
13
- import "./chunk-EWTMSDCE.js";
14
- import "./chunk-WOVNN5NW.js";
9
+ } from "./chunk-27UZ6KX2.js";
10
+ import "./chunk-PLBG7TZA.js";
11
+ import "./chunk-X4QSP3AX.js";
12
+ import "./chunk-FAU72YC2.js";
13
+ import "./chunk-BOM2BYZQ.js";
14
+ import "./chunk-3SKN5L3I.js";
15
+ import "./chunk-4ICDSQCJ.js";
15
16
  export {
16
17
  workflow_default as default,
17
18
  parseIssueReference,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-symphony/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.2",
4
4
  "license": "MIT",
5
5
  "author": "hojinzs",
6
6
  "description": "Interactive CLI for GitHub Symphony orchestration",
@@ -42,11 +42,11 @@
42
42
  "devDependencies": {
43
43
  "tsup": "^8.5.1",
44
44
  "@gh-symphony/core": "0.0.14",
45
- "@gh-symphony/control-plane": "0.0.15",
46
45
  "@gh-symphony/orchestrator": "0.0.14",
47
- "@gh-symphony/dashboard": "0.0.14",
46
+ "@gh-symphony/control-plane": "0.0.15",
48
47
  "@gh-symphony/runtime-claude": "0.0.14",
49
48
  "@gh-symphony/tracker-github": "0.0.14",
49
+ "@gh-symphony/dashboard": "0.0.14",
50
50
  "@gh-symphony/worker": "0.0.14"
51
51
  },
52
52
  "scripts": {