ralphctl 0.4.2 → 0.4.4

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 (31) hide show
  1. package/README.md +13 -11
  2. package/dist/{add-CIM72NE3.mjs → add-DVPVHENV.mjs} +7 -7
  3. package/dist/{add-GX7P7XTT.mjs → add-YVXM34RP.mjs} +6 -5
  4. package/dist/{chunk-GL7MKLLS.mjs → chunk-ACRMBVEE.mjs} +458 -181
  5. package/dist/{chunk-NUYQK5MN.mjs → chunk-BSB4EDGR.mjs} +2 -2
  6. package/dist/{chunk-YCDUVPRT.mjs → chunk-CBMFRQ4Y.mjs} +5 -73
  7. package/dist/{chunk-3QBEBKMZ.mjs → chunk-FNAAA32W.mjs} +7 -7
  8. package/dist/{chunk-JOQO4HMM.mjs → chunk-GQ2WFKBN.mjs} +11 -11
  9. package/dist/{chunk-TKPTT2UG.mjs → chunk-OFILN7QL.mjs} +798 -1023
  10. package/dist/{chunk-7JLZQICD.mjs → chunk-OGEXYSFS.mjs} +7 -7
  11. package/dist/{chunk-D2YGPLIV.mjs → chunk-PYZEQ2VK.mjs} +214 -9
  12. package/dist/{chunk-57UWLHRH.mjs → chunk-VAZ3LJBI.mjs} +12 -1
  13. package/dist/{chunk-CTP2A436.mjs → chunk-WDMLPXOD.mjs} +11 -4
  14. package/dist/{chunk-FKMKOWLA.mjs → chunk-XN2UIHBY.mjs} +84 -3
  15. package/dist/chunk-ZLWSPLWI.mjs +1117 -0
  16. package/dist/cli.mjs +72 -21
  17. package/dist/create-Z635FQKO.mjs +15 -0
  18. package/dist/{handle-BBAZJ44Y.mjs → handle-23EFF3BE.mjs} +1 -1
  19. package/dist/{mount-ISHZM36X.mjs → mount-VEV3TESX.mjs} +1702 -1202
  20. package/dist/{project-2IE7VWDB.mjs → project-DQHF4ISP.mjs} +3 -3
  21. package/dist/prompts/check-script-discover.md +69 -0
  22. package/dist/prompts/repo-onboard.md +111 -0
  23. package/dist/prompts/sprint-feedback.md +4 -0
  24. package/dist/prompts/task-evaluation.md +44 -2
  25. package/dist/prompts/task-execution.md +5 -0
  26. package/dist/{resolver-EOE5WUMV.mjs → resolver-OVPYVW6Q.mjs} +4 -4
  27. package/dist/{sprint-OGOFEJJH.mjs → sprint-4E26AB5F.mjs} +4 -4
  28. package/dist/start-2WH4BTDB.mjs +19 -0
  29. package/package.json +6 -6
  30. package/dist/create-7WFSCMP4.mjs +0 -15
  31. package/dist/start-76JKJQIH.mjs +0 -17
@@ -1,12 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- getProjectById
4
- } from "./chunk-NUYQK5MN.mjs";
5
2
  import {
6
3
  addTicket,
7
4
  fetchIssueFromUrl,
8
5
  truncate
9
- } from "./chunk-JOQO4HMM.mjs";
6
+ } from "./chunk-GQ2WFKBN.mjs";
10
7
  import {
11
8
  EXIT_ERROR,
12
9
  exitWithCode
@@ -14,9 +11,12 @@ import {
14
11
  import {
15
12
  getPrompt
16
13
  } from "./chunk-747KW2RW.mjs";
14
+ import {
15
+ getProjectById
16
+ } from "./chunk-BSB4EDGR.mjs";
17
17
  import {
18
18
  getCurrentSprintOrThrow
19
- } from "./chunk-YCDUVPRT.mjs";
19
+ } from "./chunk-CBMFRQ4Y.mjs";
20
20
  import {
21
21
  createSpinner,
22
22
  emoji,
@@ -29,7 +29,7 @@ import {
29
29
  showError,
30
30
  showSuccess,
31
31
  showWarning
32
- } from "./chunk-FKMKOWLA.mjs";
32
+ } from "./chunk-XN2UIHBY.mjs";
33
33
  import {
34
34
  ensureError,
35
35
  wrapAsync
@@ -37,7 +37,7 @@ import {
37
37
  import {
38
38
  IOError,
39
39
  SprintStatusError
40
- } from "./chunk-57UWLHRH.mjs";
40
+ } from "./chunk-VAZ3LJBI.mjs";
41
41
 
42
42
  // src/integration/cli/commands/ticket/add.ts
43
43
  import { Result as Result2 } from "typescript-result";
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- createProject
4
- } from "./chunk-NUYQK5MN.mjs";
3
+ ProviderAiSessionAdapter,
4
+ SignalParser,
5
+ buildCheckScriptDiscoverPrompt
6
+ } from "./chunk-ZLWSPLWI.mjs";
5
7
  import {
6
8
  EXIT_ERROR,
7
9
  exitWithCode
@@ -9,11 +11,15 @@ import {
9
11
  import {
10
12
  getPrompt
11
13
  } from "./chunk-747KW2RW.mjs";
14
+ import {
15
+ createProject
16
+ } from "./chunk-BSB4EDGR.mjs";
12
17
  import {
13
18
  createSpinner,
14
19
  emoji,
15
20
  error,
16
21
  field,
22
+ getConfig,
17
23
  log,
18
24
  muted,
19
25
  showError,
@@ -21,7 +27,7 @@ import {
21
27
  showSuccess,
22
28
  showTip,
23
29
  showWarning
24
- } from "./chunk-FKMKOWLA.mjs";
30
+ } from "./chunk-XN2UIHBY.mjs";
25
31
  import {
26
32
  ensureError,
27
33
  wrapAsync
@@ -29,16 +35,17 @@ import {
29
35
  import {
30
36
  expandTilde,
31
37
  validateProjectPath
32
- } from "./chunk-CTP2A436.mjs";
38
+ } from "./chunk-WDMLPXOD.mjs";
33
39
  import {
34
40
  IOError,
35
- ProjectExistsError
36
- } from "./chunk-57UWLHRH.mjs";
41
+ ProjectExistsError,
42
+ ValidationError
43
+ } from "./chunk-VAZ3LJBI.mjs";
37
44
 
38
45
  // src/integration/cli/commands/project/add.ts
39
46
  import { existsSync as existsSync2, statSync as statSync2 } from "fs";
40
47
  import { basename, join as join3, resolve as resolve2 } from "path";
41
- import { Result as Result3 } from "typescript-result";
48
+ import { Result as Result4 } from "typescript-result";
42
49
 
43
50
  // src/integration/ui/prompts/file-browser-impl.ts
44
51
  import { readdirSync, statSync } from "fs";
@@ -328,13 +335,173 @@ function suggestCheckScript(projectPath) {
328
335
  return parts.length > 0 ? parts.join(" && ") : null;
329
336
  }
330
337
 
338
+ // src/integration/ai/discover-check-script.ts
339
+ var DISCOVERY_TIMEOUT_MS = 3e4;
340
+ async function discoverCheckScriptWithAi(repoPath, aiSession, signalParser) {
341
+ const prompt = buildCheckScriptDiscoverPrompt(repoPath);
342
+ const session = aiSession.spawnHeadless(prompt, { cwd: repoPath });
343
+ const timeout = new Promise((resolve3) => {
344
+ setTimeout(() => {
345
+ resolve3(null);
346
+ }, DISCOVERY_TIMEOUT_MS).unref();
347
+ });
348
+ try {
349
+ const result = await Promise.race([session, timeout]);
350
+ if (!result) return null;
351
+ const signals = signalParser.parseSignals(result.output);
352
+ const discovery = signals.find((s) => s.type === "check-script-discovery");
353
+ return discovery ? discovery.command : null;
354
+ } catch {
355
+ return null;
356
+ }
357
+ }
358
+
359
+ // src/integration/config/schema-provider.ts
360
+ import { Result as Result3 } from "typescript-result";
361
+
362
+ // src/domain/config-schema.ts
363
+ var ConfigSchemaDefinition = {
364
+ currentSprint: {
365
+ key: "currentSprint",
366
+ label: "Current Sprint",
367
+ type: "string",
368
+ default: null,
369
+ description: "Currently active sprint ID (set by `sprint start`, cleared on `sprint close`)",
370
+ validation: (val) => val === null || typeof val === "string" && val.length > 0,
371
+ scope: "global"
372
+ },
373
+ aiProvider: {
374
+ key: "aiProvider",
375
+ label: "AI Provider",
376
+ type: "enum",
377
+ enum: ["claude", "copilot"],
378
+ default: null,
379
+ description: "AI provider for task execution (Claude Code or GitHub Copilot CLI)",
380
+ validation: (val) => val === null || typeof val === "string" && ["claude", "copilot"].includes(val),
381
+ scope: "global"
382
+ },
383
+ evaluationIterations: {
384
+ key: "evaluationIterations",
385
+ label: "Evaluation Iterations",
386
+ type: "integer",
387
+ min: 0,
388
+ max: 10,
389
+ default: 1,
390
+ description: "Number of fix-attempt iterations after initial evaluation; 0 = disabled. Higher values allow more refinement rounds.",
391
+ validation: (val) => typeof val === "number" && Number.isInteger(val) && val >= 0 && val <= 10,
392
+ scope: "sprint"
393
+ },
394
+ aiCheckScriptDiscovery: {
395
+ key: "aiCheckScriptDiscovery",
396
+ label: "AI Check-Script Discovery",
397
+ type: "boolean",
398
+ default: false,
399
+ description: "When static ecosystem detection cannot suggest a check script during `project add`, ask the configured AI provider to inspect the repo and propose one. User approval is always required before saving. Disabled by default \u2014 enable with `ralphctl config set aiCheckScriptDiscovery true`.",
400
+ validation: (val) => typeof val === "boolean",
401
+ scope: "user"
402
+ }
403
+ // Phase 2+ additions can be added here as single schema entries
404
+ // maxTaskTurns: { ... },
405
+ // checkScriptTimeout: { ... },
406
+ };
407
+ function getSchemaEntry(key) {
408
+ return ConfigSchemaDefinition[key];
409
+ }
410
+ function getAllSchemaEntries() {
411
+ return Object.values(ConfigSchemaDefinition);
412
+ }
413
+ function getDefaultValue(key) {
414
+ return ConfigSchemaDefinition[key].default;
415
+ }
416
+
417
+ // src/integration/config/schema-provider.ts
418
+ function getAllConfigSchemaEntries() {
419
+ return getAllSchemaEntries();
420
+ }
421
+ function getConfigSchemaEntry(key) {
422
+ return getSchemaEntry(key);
423
+ }
424
+ function getConfigDefaultValue(key) {
425
+ return getDefaultValue(key);
426
+ }
427
+ function validateConfigValue(key, value) {
428
+ if (!(key in ConfigSchemaDefinition)) {
429
+ return Result3.error(new ValidationError(`Unknown config key: ${key}`, key));
430
+ }
431
+ const schemaKey = key;
432
+ const entry = getConfigSchemaEntry(schemaKey);
433
+ if (!entry.validation(value)) {
434
+ let errorMsg = `Invalid value for ${key}`;
435
+ if (entry.type === "enum" && entry.enum) {
436
+ errorMsg += `: must be one of ${entry.enum.join(", ")}`;
437
+ } else if (entry.type === "integer" || entry.type === "number") {
438
+ const constraints = [];
439
+ if (entry.min !== void 0) constraints.push(`min: ${String(entry.min)}`);
440
+ if (entry.max !== void 0) constraints.push(`max: ${String(entry.max)}`);
441
+ if (constraints.length > 0) errorMsg += ` (${constraints.join(", ")})`;
442
+ }
443
+ return Result3.error(new ValidationError(errorMsg, key));
444
+ }
445
+ return Result3.ok(value);
446
+ }
447
+ function parseConfigValue(key, stringValue) {
448
+ if (!(key in ConfigSchemaDefinition)) {
449
+ return Result3.error(new ValidationError(`Unknown config key: ${key}`, key));
450
+ }
451
+ const schemaKey = key;
452
+ const entry = getConfigSchemaEntry(schemaKey);
453
+ let parsedValue;
454
+ try {
455
+ switch (entry.type) {
456
+ case "string":
457
+ parsedValue = stringValue === "null" ? null : stringValue;
458
+ break;
459
+ case "integer":
460
+ parsedValue = stringValue === "null" ? null : Number.parseInt(stringValue, 10);
461
+ if (Number.isNaN(parsedValue)) {
462
+ return Result3.error(new ValidationError(`Expected integer for ${key}, got ${stringValue}`, key));
463
+ }
464
+ break;
465
+ case "number":
466
+ parsedValue = stringValue === "null" ? null : Number.parseFloat(stringValue);
467
+ if (Number.isNaN(parsedValue)) {
468
+ return Result3.error(new ValidationError(`Expected number for ${key}, got ${stringValue}`, key));
469
+ }
470
+ break;
471
+ case "boolean":
472
+ if (stringValue.toLowerCase() === "true" || stringValue === "1") {
473
+ parsedValue = true;
474
+ } else if (stringValue.toLowerCase() === "false" || stringValue === "0") {
475
+ parsedValue = false;
476
+ } else if (stringValue === "null") {
477
+ parsedValue = null;
478
+ } else {
479
+ return Result3.error(new ValidationError(`Expected boolean for ${key}, got ${stringValue}`, key));
480
+ }
481
+ break;
482
+ case "enum":
483
+ if (stringValue === "null") {
484
+ parsedValue = null;
485
+ } else {
486
+ parsedValue = stringValue;
487
+ }
488
+ break;
489
+ }
490
+ } catch (err) {
491
+ return Result3.error(
492
+ new ValidationError(`Failed to parse ${key}: ${err instanceof Error ? err.message : String(err)}`, key)
493
+ );
494
+ }
495
+ return validateConfigValue(key, parsedValue);
496
+ }
497
+
331
498
  // src/integration/cli/commands/project/add.ts
332
499
  function validateSlug(slug) {
333
500
  return /^[a-z0-9-]+$/.test(slug);
334
501
  }
335
502
  function isGitRepo2(path) {
336
503
  const gitDir = join3(path, ".git");
337
- const r = Result3.try(() => existsSync2(gitDir) && statSync2(gitDir).isDirectory());
504
+ const r = Result4.try(() => existsSync2(gitDir) && statSync2(gitDir).isDirectory());
338
505
  return r.ok ? r.value : false;
339
506
  }
340
507
  function hasAiInstructions(repoPath) {
@@ -342,11 +509,12 @@ function hasAiInstructions(repoPath) {
342
509
  }
343
510
  async function addCheckScriptToRepository(repo) {
344
511
  let suggested = null;
345
- const detectR = Result3.try(() => detectCheckScriptCandidates(repo.path));
512
+ const detectR = Result4.try(() => detectCheckScriptCandidates(repo.path));
346
513
  if (detectR.ok && detectR.value) {
347
514
  log.success(` Detected: ${detectR.value.typeLabel}`);
348
515
  suggested = suggestCheckScript(repo.path);
349
516
  }
517
+ suggested ??= await tryAiCheckScriptDiscovery(repo.path);
350
518
  const checkInput = await getPrompt().input({
351
519
  message: " Check script (optional):",
352
520
  default: suggested ?? void 0
@@ -357,6 +525,35 @@ async function addCheckScriptToRepository(repo) {
357
525
  checkScript
358
526
  };
359
527
  }
528
+ async function tryAiCheckScriptDiscovery(repoPath) {
529
+ const config = await getConfig();
530
+ const enabled = config.aiCheckScriptDiscovery ?? getConfigDefaultValue("aiCheckScriptDiscovery");
531
+ if (!enabled) return null;
532
+ if (!config.aiProvider) return null;
533
+ const wantAi = await getPrompt().confirm({
534
+ message: " No ecosystem detected. Ask AI to inspect the repo and suggest a check script?",
535
+ default: true
536
+ });
537
+ if (!wantAi) return null;
538
+ const spinner = createSpinner(" Asking AI to discover check script...").start();
539
+ const aiSession = new ProviderAiSessionAdapter();
540
+ const signalParser = new SignalParser();
541
+ const discoverR = await wrapAsync(async () => {
542
+ await aiSession.ensureReady();
543
+ return discoverCheckScriptWithAi(repoPath, aiSession, signalParser);
544
+ }, ensureError);
545
+ if (!discoverR.ok) {
546
+ spinner.fail("AI discovery unavailable");
547
+ return null;
548
+ }
549
+ const suggestion = discoverR.value;
550
+ if (suggestion) {
551
+ spinner.succeed("AI suggestion ready (review before saving)");
552
+ } else {
553
+ spinner.fail("AI returned no suggestion");
554
+ }
555
+ return suggestion;
556
+ }
360
557
  async function projectAddCommand(options = {}) {
361
558
  let name;
362
559
  let displayName;
@@ -577,6 +774,14 @@ Configuring: ${firstRepo.name ?? basename(firstRepo.path)}`);
577
774
  export {
578
775
  PromptCancelledError,
579
776
  escapableSelect,
777
+ detectCheckScriptCandidates,
778
+ suggestCheckScript,
779
+ discoverCheckScriptWithAi,
780
+ getAllSchemaEntries,
781
+ getAllConfigSchemaEntries,
782
+ getConfigDefaultValue,
783
+ validateConfigValue,
784
+ parseConfigValue,
580
785
  addCheckScriptToRepository,
581
786
  projectAddCommand
582
787
  };
@@ -145,6 +145,16 @@ var BranchPreflightError = class extends DomainError {
145
145
  this.expectedBranch = expectedBranch;
146
146
  }
147
147
  };
148
+ var ExecutionAlreadyRunningError = class extends DomainError {
149
+ code = "EXECUTION_ALREADY_RUNNING";
150
+ projectName;
151
+ existingExecutionId;
152
+ constructor(projectName, existingExecutionId) {
153
+ super(`An execution is already running for project '${projectName}' (id: ${existingExecutionId}).`);
154
+ this.projectName = projectName;
155
+ this.existingExecutionId = existingExecutionId;
156
+ }
157
+ };
148
158
 
149
159
  export {
150
160
  DomainError,
@@ -164,5 +174,6 @@ export {
164
174
  DependencyCycleError,
165
175
  IssueFetchError,
166
176
  StepError,
167
- BranchPreflightError
177
+ BranchPreflightError,
178
+ ExecutionAlreadyRunningError
168
179
  };
@@ -3,7 +3,7 @@ import {
3
3
  IOError,
4
4
  StorageError,
5
5
  ValidationError
6
- } from "./chunk-57UWLHRH.mjs";
6
+ } from "./chunk-VAZ3LJBI.mjs";
7
7
 
8
8
  // src/integration/persistence/paths.ts
9
9
  import { isAbsolute, join, resolve, sep } from "path";
@@ -191,10 +191,11 @@ async function readTextFile(filePath) {
191
191
  // src/domain/models.ts
192
192
  import { z } from "zod";
193
193
  var SprintStatusSchema = z.enum(["draft", "active", "closed"]);
194
- var TaskStatusSchema = z.enum(["todo", "in_progress", "done"]);
194
+ var TaskStatusSchema = z.enum(["todo", "in_progress", "done", "cancelled"]);
195
195
  var RequirementStatusSchema = z.enum(["pending", "approved"]);
196
196
  var EvaluationStatusSchema = z.enum(["passed", "failed", "malformed", "plateau"]);
197
197
  var IdSchema = z.string().min(1);
198
+ var CURRENT_ONBOARDING_VERSION = 1;
198
199
  var RepositorySchema = z.object({
199
200
  id: IdSchema,
200
201
  // UUID8, stable across renames
@@ -204,8 +205,12 @@ var RepositorySchema = z.object({
204
205
  // Absolute path
205
206
  checkScript: z.string().optional(),
206
207
  // e.g., "pnpm install && pnpm typecheck && pnpm lint && pnpm test"
207
- checkTimeout: z.number().positive().optional()
208
+ checkTimeout: z.number().positive().optional(),
208
209
  // Per-repo timeout in ms (overrides RALPHCTL_SETUP_TIMEOUT_MS)
210
+ // Stamped by `project onboard` on success. Absence means the repo was never
211
+ // onboarded; `doctor` surfaces a hint. A value < CURRENT_ONBOARDING_VERSION
212
+ // means the contract has drifted and re-onboarding is recommended.
213
+ onboardingVersion: z.number().int().nonnegative().optional()
209
214
  });
210
215
  var ProjectSchema = z.object({
211
216
  id: IdSchema,
@@ -290,7 +295,8 @@ var ConfigSchema = z.object({
290
295
  currentSprint: z.string().nullable().default(null),
291
296
  aiProvider: AiProviderSchema.nullable().default(null),
292
297
  editor: z.string().nullable().default(null),
293
- evaluationIterations: z.number().int().min(0).optional()
298
+ evaluationIterations: z.number().int().min(0).optional(),
299
+ aiCheckScriptDiscovery: z.boolean().optional()
294
300
  });
295
301
  function getRequirementsOutputJsonSchema() {
296
302
  return JSON.stringify(z.toJSONSchema(RefinedRequirementsSchema), null, 2);
@@ -342,6 +348,7 @@ export {
342
348
  SprintStatusSchema,
343
349
  TaskStatusSchema,
344
350
  RequirementStatusSchema,
351
+ CURRENT_ONBOARDING_VERSION,
345
352
  ProjectsSchema,
346
353
  TasksSchema,
347
354
  ImportTasksSchema,
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ unwrapOrThrow
4
+ } from "./chunk-IWXBJD2D.mjs";
5
+ import {
6
+ ConfigSchema,
7
+ fileExists,
8
+ getConfigPath,
9
+ readValidatedJson,
10
+ writeValidatedJson
11
+ } from "./chunk-WDMLPXOD.mjs";
2
12
 
3
13
  // src/integration/ui/theme/theme.ts
4
14
  import { bold, cyan, gray, green, red, yellow } from "colorette";
@@ -295,11 +305,17 @@ function labelValue(label, value, labelWidth = DETAIL_LABEL_WIDTH) {
295
305
  }
296
306
  function formatTaskStatus(status) {
297
307
  const e = getStatusEmoji(status);
298
- const labels = { todo: "To Do", in_progress: "In Progress", done: "Done" };
308
+ const labels = {
309
+ todo: "To Do",
310
+ in_progress: "In Progress",
311
+ done: "Done",
312
+ cancelled: "Cancelled"
313
+ };
299
314
  const statusColors = {
300
315
  todo: colors.muted,
301
316
  in_progress: colors.warning,
302
- done: colors.success
317
+ done: colors.success,
318
+ cancelled: colors.muted
303
319
  };
304
320
  return (statusColors[status] ?? colors.muted)(`${e} ${labels[status] ?? status}`);
305
321
  }
@@ -467,6 +483,60 @@ function renderTable(columns, rows, options = {}) {
467
483
  return result.join("\n");
468
484
  }
469
485
 
486
+ // src/integration/persistence/config.ts
487
+ var DEFAULT_EVALUATION_ITERATIONS = 1;
488
+ var DEFAULT_CONFIG = {
489
+ currentSprint: null,
490
+ aiProvider: null,
491
+ editor: null
492
+ };
493
+ async function getConfig() {
494
+ const configPath = getConfigPath();
495
+ if (!await fileExists(configPath)) {
496
+ return DEFAULT_CONFIG;
497
+ }
498
+ return unwrapOrThrow(await readValidatedJson(configPath, ConfigSchema));
499
+ }
500
+ async function saveConfig(config) {
501
+ unwrapOrThrow(await writeValidatedJson(getConfigPath(), config, ConfigSchema));
502
+ }
503
+ async function getCurrentSprint() {
504
+ const config = await getConfig();
505
+ return config.currentSprint;
506
+ }
507
+ async function setCurrentSprint(sprintId) {
508
+ const config = await getConfig();
509
+ config.currentSprint = sprintId;
510
+ await saveConfig(config);
511
+ }
512
+ async function getAiProvider() {
513
+ const config = await getConfig();
514
+ return config.aiProvider ?? null;
515
+ }
516
+ async function setAiProvider(provider) {
517
+ const config = await getConfig();
518
+ config.aiProvider = provider;
519
+ await saveConfig(config);
520
+ }
521
+ async function getEditor() {
522
+ const config = await getConfig();
523
+ return config.editor ?? null;
524
+ }
525
+ async function setEditor(editor) {
526
+ const config = await getConfig();
527
+ config.editor = editor;
528
+ await saveConfig(config);
529
+ }
530
+ async function getEvaluationIterations() {
531
+ const config = await getConfig();
532
+ return config.evaluationIterations ?? DEFAULT_EVALUATION_ITERATIONS;
533
+ }
534
+ async function setEvaluationIterations(iterations) {
535
+ const config = await getConfig();
536
+ config.evaluationIterations = iterations;
537
+ await saveConfig(config);
538
+ }
539
+
470
540
  export {
471
541
  emoji,
472
542
  colors,
@@ -491,6 +561,7 @@ export {
491
561
  showRandomQuote,
492
562
  printCountSummary,
493
563
  printBanner,
564
+ isTTY,
494
565
  terminalBell,
495
566
  createSpinner,
496
567
  field,
@@ -504,5 +575,15 @@ export {
504
575
  horizontalLine,
505
576
  renderCard,
506
577
  progressBar,
507
- renderTable
578
+ renderTable,
579
+ getConfig,
580
+ saveConfig,
581
+ getCurrentSprint,
582
+ setCurrentSprint,
583
+ getAiProvider,
584
+ setAiProvider,
585
+ getEditor,
586
+ setEditor,
587
+ getEvaluationIterations,
588
+ setEvaluationIterations
508
589
  };