@gh-symphony/cli 0.2.0 → 0.2.3

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/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-MVRF7BES.js";
8
8
  import {
9
9
  resolveConfigDir
10
- } from "./chunk-WOVNN5NW.js";
10
+ } from "./chunk-4ICDSQCJ.js";
11
11
 
12
12
  // src/index.ts
13
13
  import { realpathSync } from "fs";
@@ -60,7 +60,6 @@ var COMMAND_OPTIONS = {
60
60
  "workflow:preview": ["--file", "--sample", "--attempt", ...GLOBAL_OPTIONS],
61
61
  setup: [
62
62
  "--non-interactive",
63
- "--assigned-only",
64
63
  "--output",
65
64
  "--skip-skills",
66
65
  "--skip-context",
@@ -77,7 +76,7 @@ var COMMAND_OPTIONS = {
77
76
  upgrade: [...GLOBAL_OPTIONS],
78
77
  repo: ["init", "start", "status", "stop", "run", "recover", "logs", "explain"],
79
78
  "repo:init": ["--repo-dir", "--workflow-file", ...GLOBAL_OPTIONS],
80
- "repo:start": ["--daemon", "-d", "--once", "--http", "--web", "--log-level", ...GLOBAL_OPTIONS],
79
+ "repo:start": ["--daemon", "-d", "--once", "--assigned-only", "--http", "--web", "--log-level", ...GLOBAL_OPTIONS],
81
80
  "repo:status": ["--watch", "-w", ...GLOBAL_OPTIONS],
82
81
  "repo:stop": ["--force", ...GLOBAL_OPTIONS],
83
82
  "repo:run": ["--watch", ...GLOBAL_OPTIONS],
@@ -234,7 +233,7 @@ ${bashFunction}complete -F _gh_symphony_completion gh-symphony
234
233
  }
235
234
 
236
235
  // src/commands/help.ts
237
- var DESCRIPTION_COLUMN = 23;
236
+ var DESCRIPTION_COLUMN = 30;
238
237
  var COMMAND_COLUMN_WIDTH = DESCRIPTION_COLUMN - 2;
239
238
  var HELP_SECTIONS = [
240
239
  {
@@ -289,6 +288,10 @@ var HELP_SECTIONS = [
289
288
  name: "repo start --daemon",
290
289
  description: "Start the orchestrator in the background"
291
290
  },
291
+ {
292
+ name: "repo start --assigned-only",
293
+ description: "Process only issues assigned to the authenticated user"
294
+ },
292
295
  {
293
296
  name: "repo stop",
294
297
  description: "Stop the background orchestrator"
@@ -414,13 +417,13 @@ function createRemovedCommandHandler(message) {
414
417
 
415
418
  // src/index.ts
416
419
  var COMMANDS = {
417
- workflow: () => import("./workflow-KB3TX5Z4.js"),
418
- setup: () => import("./setup-PD27LSPP.js"),
419
- doctor: () => import("./doctor-JPNA7OCD.js"),
420
- upgrade: () => import("./upgrade-HRI3KEO7.js"),
421
- repo: () => import("./repo-OJLSMOR3.js"),
422
- config: () => import("./config-cmd-2ADPUYWA.js"),
423
- version: () => import("./version-JSBTKS6Q.js")
420
+ workflow: () => import("./workflow-ZPERNZJT.js"),
421
+ setup: () => import("./setup-KZ3U53PY.js"),
422
+ doctor: () => import("./doctor-GIJAH7MA.js"),
423
+ upgrade: () => import("./upgrade-K2PNQNWE.js"),
424
+ repo: () => import("./repo-LNO3Q3O7.js"),
425
+ config: () => import("./config-cmd-AOZVS6GU.js"),
426
+ version: () => import("./version-E45DDQPQ.js")
424
427
  };
425
428
  function addGlobalOptions(command) {
426
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");
@@ -562,13 +565,12 @@ function createProgram() {
562
565
  await invokeHandler("workflow", args, values);
563
566
  });
564
567
  addGlobalOptions(
565
- program.command("setup").description("Run the one-command first-run setup flow").option("--non-interactive", "Run without prompts").option("--assigned-only", "Limit processing to assigned issues").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").allowExcessArguments(false)
568
+ program.command("setup").description("Run the one-command first-run setup flow").option("--non-interactive", "Run without prompts").option("--output <path>", "Write WORKFLOW.md to a custom path").option("--skip-skills", "Skip runtime skill generation").option("--skip-context", "Skip .gh-symphony/context.yaml generation").allowExcessArguments(false)
566
569
  ).action(async function() {
567
570
  markInvoked();
568
571
  const values = this.optsWithGlobals();
569
572
  const args = [];
570
573
  pushOption(args, "--non-interactive", values.nonInteractive);
571
- pushOption(args, "--assigned-only", values.assignedOnly);
572
574
  pushOption(args, "--output", values.output);
573
575
  pushOption(args, "--skip-skills", values.skipSkills);
574
576
  pushOption(args, "--skip-context", values.skipContext);
@@ -581,6 +583,9 @@ function createProgram() {
581
583
  ).option(
582
584
  "--smoke",
583
585
  "Run a safe live issue readiness check without dispatching work"
586
+ ).option(
587
+ "--bundle [path]",
588
+ "Export a redacted support bundle for shareable diagnostics"
584
589
  ).option("--issue <owner/repo#number>", "Live issue to validate").addOption(new Option("--project <projectId>").hideHelp()).allowExcessArguments(false)
585
590
  ).action(async function() {
586
591
  markInvoked();
@@ -589,6 +594,7 @@ function createProgram() {
589
594
  pushOption(args, "--project-id", resolveProjectId(values));
590
595
  pushOption(args, "--fix", values.fix);
591
596
  pushOption(args, "--smoke", values.smoke);
597
+ pushOption(args, "--bundle", values.bundle);
592
598
  pushOption(args, "--issue", values.issue);
593
599
  await invokeHandler("doctor", args, values);
594
600
  });
@@ -696,7 +702,7 @@ function createProgram() {
696
702
  await invokeHandler("repo", args, values);
697
703
  });
698
704
  addGlobalOptions(
699
- repo.command("start").description("Start the orchestrator for the current repository").option("-d, --daemon", "Start in daemon mode").option("--once", "Run a single orchestration tick and exit").option(
705
+ repo.command("start").description("Start the orchestrator for the current repository").option("-d, --daemon", "Start in daemon mode").option("--once", "Run a single orchestration tick and exit").option("--assigned-only", "Limit this run to assigned issues").option(
700
706
  "--http [port]",
701
707
  "Expose dashboard and refresh endpoints over HTTP"
702
708
  ).option(
@@ -709,6 +715,7 @@ function createProgram() {
709
715
  const args = ["start", ...this.args];
710
716
  pushOption(args, "--daemon", values.daemon);
711
717
  pushOption(args, "--once", values.once);
718
+ pushOption(args, "--assigned-only", values.assignedOnly);
712
719
  pushOption(args, "--http", values.http);
713
720
  pushOption(args, "--web", values.web);
714
721
  pushOption(args, "--log-level", values.logLevel);
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  initRepoRuntime,
19
19
  parseRepoRuntimeFlags
20
- } from "./chunk-C44DYDNU.js";
20
+ } from "./chunk-DLZ2XHWY.js";
21
21
  import {
22
22
  OrchestratorService,
23
23
  acquireProjectLock,
@@ -33,16 +33,16 @@ import {
33
33
  resolveOrchestratorLogLevel,
34
34
  resolveTrackerAdapter,
35
35
  runCli
36
- } from "./chunk-CTTFIZYG.js";
37
- import "./chunk-B6OHDUSH.js";
36
+ } from "./chunk-ZGNAAHLD.js";
37
+ import "./chunk-FAU72YC2.js";
38
38
  import {
39
39
  resolveRepoRuntimeRoot,
40
40
  resolveRuntimeRoot
41
- } from "./chunk-6I753NYO.js";
41
+ } from "./chunk-RZ3WO7OV.js";
42
42
  import {
43
43
  GhAuthError,
44
44
  getGhToken
45
- } from "./chunk-Z3NZOPLZ.js";
45
+ } from "./chunk-BOM2BYZQ.js";
46
46
  import {
47
47
  WorkflowConfigStore,
48
48
  deriveIssueWorkspaceKeyFromIdentifier,
@@ -52,14 +52,14 @@ import {
52
52
  parseRecentEvents,
53
53
  readJsonFile,
54
54
  safeReadDir
55
- } from "./chunk-Q3UEPUE3.js";
55
+ } from "./chunk-3SKN5L3I.js";
56
56
  import {
57
57
  daemonPidPath,
58
58
  httpStatusPath,
59
59
  loadActiveProjectConfig,
60
60
  orchestratorLogPath,
61
61
  writeJsonFile
62
- } from "./chunk-WOVNN5NW.js";
62
+ } from "./chunk-4ICDSQCJ.js";
63
63
 
64
64
  // src/commands/logs.ts
65
65
  import { readFile, readdir, stat } from "fs/promises";
@@ -1454,6 +1454,10 @@ function parseStartArgs(args) {
1454
1454
  parsed.once = true;
1455
1455
  continue;
1456
1456
  }
1457
+ if (arg === "--assigned-only") {
1458
+ parsed.assignedOnly = true;
1459
+ continue;
1460
+ }
1457
1461
  if (arg === "--http") {
1458
1462
  const value = args[i + 1];
1459
1463
  if (!value || value.startsWith("-")) {
@@ -1716,7 +1720,7 @@ var handler5 = async (args, options) => {
1716
1720
  process.stderr.write(`${parsed.error}
1717
1721
  `);
1718
1722
  process.stderr.write(
1719
- "Usage: gh-symphony repo start [--daemon] [--once] [--http [port]] [--web [port]]\n"
1723
+ "Usage: gh-symphony repo start [--daemon] [--once] [--assigned-only] [--http [port]] [--web [port]]\n"
1720
1724
  );
1721
1725
  process.exitCode = 2;
1722
1726
  return;
@@ -1764,7 +1768,8 @@ var handler5 = async (args, options) => {
1764
1768
  projectId,
1765
1769
  parsed.logLevel,
1766
1770
  parsed.httpPort,
1767
- parsed.webPort
1771
+ parsed.webPort,
1772
+ parsed.assignedOnly === true
1768
1773
  );
1769
1774
  return;
1770
1775
  }
@@ -1786,6 +1791,7 @@ var handler5 = async (args, options) => {
1786
1791
  let isFirst = true;
1787
1792
  const service = new OrchestratorService(store, projectConfig, {
1788
1793
  logLevel,
1794
+ assignedOnly: parsed.assignedOnly,
1789
1795
  onTick: async (snapshot) => {
1790
1796
  try {
1791
1797
  logTickResult(snapshot, prevSnapshot, isFirst);
@@ -2018,7 +2024,7 @@ async function tailWorkerLog(runtimeRoot, projectId, runId, issueIdentifier) {
2018
2024
  }
2019
2025
  }
2020
2026
  var start_default = handler5;
2021
- async function startDaemon(options, projectId, logLevel, httpPort, webPort) {
2027
+ async function startDaemon(options, projectId, logLevel, httpPort, webPort, assignedOnly = false) {
2022
2028
  const logPath = orchestratorLogPath(options.configDir, projectId);
2023
2029
  await mkdir(dirname2(logPath), { recursive: true });
2024
2030
  const { openSync } = await import("fs");
@@ -2029,6 +2035,7 @@ async function startDaemon(options, projectId, logLevel, httpPort, webPort) {
2029
2035
  process.argv[1],
2030
2036
  "repo",
2031
2037
  "start",
2038
+ ...assignedOnly ? ["--assigned-only"] : [],
2032
2039
  ...httpPort !== void 0 ? ["--http", String(httpPort)] : [],
2033
2040
  ...webPort !== void 0 ? ["--web", String(webPort)] : [],
2034
2041
  ...logLevel ? ["--log-level", logLevel] : []
@@ -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-F46FTZJE.js";
15
+ } from "./chunk-PLBG7TZA.js";
14
16
  import {
15
17
  initRepoRuntime
16
- } from "./chunk-C44DYDNU.js";
17
- import "./chunk-6I753NYO.js";
18
+ } from "./chunk-DLZ2XHWY.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-Q3UEPUE3.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";
@@ -46,9 +48,6 @@ function parseSetupFlags(args) {
46
48
  case "--non-interactive":
47
49
  flags.nonInteractive = true;
48
50
  break;
49
- case "--assigned-only":
50
- flags.assignedOnly = true;
51
- break;
52
51
  case "--output":
53
52
  flags.output = next;
54
53
  i += 1;
@@ -62,7 +61,7 @@ function parseSetupFlags(args) {
62
61
  default:
63
62
  if (arg?.startsWith("-")) {
64
63
  throw new Error(
65
- `Unknown option '${arg}'. Removed project/workspace flags are no longer supported; run 'gh-symphony setup' from inside the target repository. Supported flags: --non-interactive, --assigned-only, --output, --skip-skills, --skip-context.`
64
+ `Unknown option '${arg}'. Removed project/workspace flags are no longer supported; run 'gh-symphony setup' from inside the target repository. Supported flags: --non-interactive, --output, --skip-skills, --skip-context.`
66
65
  );
67
66
  }
68
67
  }
@@ -198,10 +197,17 @@ async function runNonInteractive(flags, options) {
198
197
  const { field: priorityField, ambiguous: ambiguousPriorityFields } = resolvePriorityField(projectDetail, statusField);
199
198
  if (ambiguousPriorityFields.length > 0) {
200
199
  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.
200
+ `Warning: Multiple priority-like single-select fields found (${ambiguousPriorityFields.map((field) => `"${field.name}"`).join(", ")}). Writing disabled priority scaffold in non-interactive mode.
202
201
  `
203
202
  );
204
203
  }
204
+ const priority = priorityField ? {
205
+ source: "project-field",
206
+ field: priorityField.name,
207
+ values: Object.fromEntries(
208
+ priorityField.options.map((option, index) => [option.name, index])
209
+ )
210
+ } : { source: "disabled" };
205
211
  const workflowValidation = validateStateMapping(mappings);
206
212
  if (!workflowValidation.valid) {
207
213
  process.stderr.write(
@@ -219,6 +225,8 @@ Run setup without --non-interactive for manual mapping.
219
225
  projectDetail,
220
226
  statusField,
221
227
  priorityField,
228
+ priority,
229
+ includePriorityTemplates: !priorityField,
222
230
  mappings,
223
231
  runtime: "codex",
224
232
  skipSkills: flags.skipSkills,
@@ -230,14 +238,15 @@ Run setup without --non-interactive for manual mapping.
230
238
  projectDetail,
231
239
  statusField,
232
240
  priorityField,
241
+ priority,
242
+ includePriorityTemplates: !priorityField,
233
243
  runtime: "codex",
234
244
  skipSkills: flags.skipSkills,
235
245
  skipContext: flags.skipContext
236
246
  });
237
247
  const runtime = await initRepoRuntime({
238
248
  repoDir: process.cwd(),
239
- workflowFile: workflowPath,
240
- assignedOnly: flags.assignedOnly
249
+ workflowFile: workflowPath
241
250
  });
242
251
  if (options.json) {
243
252
  process.stdout.write(
@@ -325,8 +334,12 @@ async function runInteractive(flags, _options) {
325
334
  return;
326
335
  }
327
336
  const priorityResolution = resolvePriorityField(projectDetail, statusField);
337
+ const priorityLabelNames = await collectPriorityLabelNames(
338
+ client,
339
+ projectDetail.linkedRepositories
340
+ );
328
341
  const mappings = await promptStateMappings(statusField, {
329
- stepLabel: priorityResolution.ambiguous.length > 0 ? "Step 2/4" : "Step 2/3"
342
+ stepLabel: "Step 2/3"
330
343
  });
331
344
  const workflowValidation = validateStateMapping(mappings);
332
345
  if (!workflowValidation.valid) {
@@ -340,36 +353,11 @@ async function runInteractive(flags, _options) {
340
353
  for (const warning of workflowValidation.warnings) {
341
354
  p.log.warn(` \u26A0 ${warning}`);
342
355
  }
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;
366
- const promptAssignedOnly = await abortIfCancelled(
367
- p.confirm({
368
- message: `${priorityResolution.ambiguous.length > 0 ? "Step 4/4" : "Step 3/3"} \u2014 Only process issues assigned to the authenticated GitHub user?`,
369
- initialValue: flags.assignedOnly ?? false
370
- })
371
- );
372
- const assignedOnly = flags.assignedOnly || promptAssignedOnly;
356
+ const { priority, priorityField } = await promptPriorityConfig({
357
+ priorityResolution,
358
+ labelNames: priorityLabelNames,
359
+ stepLabel: "Step 3/3"
360
+ });
373
361
  const workflowPath = resolve(flags.output ?? "WORKFLOW.md");
374
362
  const { workflowPlan, ecosystemPlan } = await planWorkflowArtifacts({
375
363
  cwd: process.cwd(),
@@ -377,6 +365,8 @@ async function runInteractive(flags, _options) {
377
365
  projectDetail,
378
366
  statusField,
379
367
  priorityField,
368
+ priority,
369
+ includePriorityTemplates: priority.source === "disabled",
380
370
  mappings,
381
371
  runtime: "codex",
382
372
  skipSkills: flags.skipSkills,
@@ -387,7 +377,6 @@ async function runInteractive(flags, _options) {
387
377
  `GitHub Project: ${projectDetail.title}`,
388
378
  `Authenticated: ${login}`,
389
379
  `Repository: current working directory`,
390
- `Assigned: ${assignedOnly ? `Only issues assigned to ${login}` : "All project issues"}`,
391
380
  "",
392
381
  renderDryRunPreview(workflowPath, workflowPlan, ecosystemPlan).trimEnd()
393
382
  ].join("\n"),
@@ -410,14 +399,15 @@ async function runInteractive(flags, _options) {
410
399
  projectDetail,
411
400
  statusField,
412
401
  priorityField,
402
+ priority,
403
+ includePriorityTemplates: priority.source === "disabled",
413
404
  runtime: "codex",
414
405
  skipSkills: flags.skipSkills,
415
406
  skipContext: flags.skipContext
416
407
  });
417
408
  const runtime = await initRepoRuntime({
418
409
  repoDir: process.cwd(),
419
- workflowFile: workflowPath,
420
- assignedOnly
410
+ workflowFile: workflowPath
421
411
  });
422
412
  writeSpinner.stop(`Setup saved for ${runtime.repository.owner}/${runtime.repository.name}.`);
423
413
  } catch (error) {
@@ -16,8 +16,8 @@ function execFileAsync(file, args, execFileImpl = execFileCallback) {
16
16
  });
17
17
  }
18
18
  function resolveCurrentCliVersion() {
19
- if ("0.2.0".length > 0) {
20
- return "0.2.0";
19
+ if ("0.2.3".length > 0) {
20
+ return "0.2.3";
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.0";
5
+ const version = "0.2.3";
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-B6OHDUSH.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-Q3UEPUE3.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,13 +6,13 @@ import {
6
6
  resetWorkflowCommandDependenciesForTest,
7
7
  setWorkflowCommandDependenciesForTest,
8
8
  workflow_default
9
- } from "./chunk-JU3WSGMZ.js";
10
- import "./chunk-F46FTZJE.js";
11
- import "./chunk-CTTFIZYG.js";
12
- import "./chunk-B6OHDUSH.js";
13
- import "./chunk-Z3NZOPLZ.js";
14
- import "./chunk-Q3UEPUE3.js";
15
- import "./chunk-WOVNN5NW.js";
9
+ } from "./chunk-AA3T5AAJ.js";
10
+ import "./chunk-PLBG7TZA.js";
11
+ import "./chunk-ZGNAAHLD.js";
12
+ import "./chunk-FAU72YC2.js";
13
+ import "./chunk-BOM2BYZQ.js";
14
+ import "./chunk-3SKN5L3I.js";
15
+ import "./chunk-4ICDSQCJ.js";
16
16
  export {
17
17
  workflow_default as default,
18
18
  parseIssueReference,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-symphony/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "license": "MIT",
5
5
  "author": "hojinzs",
6
6
  "description": "Interactive CLI for GitHub Symphony orchestration",
@@ -41,13 +41,13 @@
41
41
  },
42
42
  "devDependencies": {
43
43
  "tsup": "^8.5.1",
44
- "@gh-symphony/control-plane": "0.0.15",
45
- "@gh-symphony/dashboard": "0.0.14",
46
44
  "@gh-symphony/core": "0.0.14",
45
+ "@gh-symphony/dashboard": "0.0.14",
46
+ "@gh-symphony/orchestrator": "0.0.14",
47
47
  "@gh-symphony/runtime-claude": "0.0.14",
48
- "@gh-symphony/tracker-github": "0.0.14",
49
48
  "@gh-symphony/worker": "0.0.14",
50
- "@gh-symphony/orchestrator": "0.0.14"
49
+ "@gh-symphony/tracker-github": "0.0.14",
50
+ "@gh-symphony/control-plane": "0.0.15"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsup",