ralphctl 0.4.1 → 0.4.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.
Files changed (36) hide show
  1. package/README.md +13 -11
  2. package/dist/{add-CIM72NE3.mjs → add-MG26JWBP.mjs} +6 -6
  3. package/dist/{add-GX7P7XTT.mjs → add-ZZYL4BSF.mjs} +5 -4
  4. package/dist/chunk-2FT37OZX.mjs +1071 -0
  5. package/dist/{chunk-CTP2A436.mjs → chunk-D2HWXEHH.mjs} +9 -2
  6. package/dist/{chunk-JOQO4HMM.mjs → chunk-EGUFQNRB.mjs} +10 -10
  7. package/dist/{chunk-3HJNVQ7N.mjs → chunk-LCY32RW4.mjs} +621 -976
  8. package/dist/{chunk-NUYQK5MN.mjs → chunk-LDSG7G2T.mjs} +1 -1
  9. package/dist/{chunk-7JLZQICD.mjs → chunk-MDE6KPJQ.mjs} +6 -6
  10. package/dist/{chunk-3QBEBKMZ.mjs → chunk-Q4AVHUZL.mjs} +7 -7
  11. package/dist/{chunk-YCDUVPRT.mjs → chunk-RQGD5WS6.mjs} +4 -72
  12. package/dist/{chunk-D2YGPLIV.mjs → chunk-TDBEEHTS.mjs} +213 -8
  13. package/dist/{chunk-SM4GGZSU.mjs → chunk-WOMGKKZY.mjs} +152 -179
  14. package/dist/{chunk-FKMKOWLA.mjs → chunk-WZTY77GY.mjs} +75 -1
  15. package/dist/cli.mjs +68 -19
  16. package/dist/{create-7WFSCMP4.mjs → create-PQK6KKRD.mjs} +5 -5
  17. package/dist/{handle-BBAZJ44Y.mjs → handle-SYVCFI6Y.mjs} +1 -1
  18. package/dist/{mount-2N6H5CWA.mjs → mount-2ANLHHQE.mjs} +556 -318
  19. package/dist/{project-2IE7VWDB.mjs → project-JF47ZWMF.mjs} +2 -2
  20. package/dist/prompts/check-script-discover.md +69 -0
  21. package/dist/prompts/ideate-auto.md +26 -1
  22. package/dist/prompts/ideate.md +5 -1
  23. package/dist/prompts/plan-auto.md +30 -2
  24. package/dist/prompts/plan-common-examples.md +82 -0
  25. package/dist/prompts/plan-common.md +26 -78
  26. package/dist/prompts/plan-interactive.md +6 -2
  27. package/dist/prompts/repo-onboard.md +111 -0
  28. package/dist/prompts/sprint-feedback.md +6 -2
  29. package/dist/prompts/task-evaluation.md +25 -10
  30. package/dist/prompts/task-execution.md +13 -13
  31. package/dist/prompts/ticket-refine.md +4 -0
  32. package/dist/prompts/validation-checklist.md +4 -0
  33. package/dist/{resolver-EOE5WUMV.mjs → resolver-PG2DZEBX.mjs} +3 -3
  34. package/dist/{sprint-OGOFEJJH.mjs → sprint-54DOSIJK.mjs} +3 -3
  35. package/dist/{start-IUDCXIEA.mjs → start-2SZTBKGF.mjs} +7 -5
  36. package/package.json +6 -6
@@ -8,7 +8,7 @@ import {
8
8
  readValidatedJson,
9
9
  validateProjectPath,
10
10
  writeValidatedJson
11
- } from "./chunk-CTP2A436.mjs";
11
+ } from "./chunk-D2HWXEHH.mjs";
12
12
  import {
13
13
  ParseError,
14
14
  ProjectExistsError,
@@ -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-EGUFQNRB.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-LDSG7G2T.mjs";
17
17
  import {
18
18
  getCurrentSprintOrThrow
19
- } from "./chunk-YCDUVPRT.mjs";
19
+ } from "./chunk-RQGD5WS6.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-WZTY77GY.mjs";
33
33
  import {
34
34
  ensureError,
35
35
  wrapAsync
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- listProjects
4
- } from "./chunk-NUYQK5MN.mjs";
5
2
  import {
6
3
  EXIT_ERROR,
7
4
  exitWithCode
@@ -10,19 +7,22 @@ import {
10
7
  getPrompt
11
8
  } from "./chunk-747KW2RW.mjs";
12
9
  import {
13
- createSprint,
14
- setCurrentSprint
15
- } from "./chunk-YCDUVPRT.mjs";
10
+ listProjects
11
+ } from "./chunk-LDSG7G2T.mjs";
12
+ import {
13
+ createSprint
14
+ } from "./chunk-RQGD5WS6.mjs";
16
15
  import {
17
16
  emoji,
18
17
  field,
19
18
  formatSprintStatus,
20
19
  icons,
20
+ setCurrentSprint,
21
21
  showError,
22
22
  showNextStep,
23
23
  showRandomQuote,
24
24
  showSuccess
25
- } from "./chunk-FKMKOWLA.mjs";
25
+ } from "./chunk-WZTY77GY.mjs";
26
26
 
27
27
  // src/integration/cli/commands/sprint/create.ts
28
28
  async function sprintCreateCommand(options = {}) {
@@ -1,12 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ getCurrentSprint,
3
4
  log
4
- } from "./chunk-FKMKOWLA.mjs";
5
- import {
6
- unwrapOrThrow
7
- } from "./chunk-IWXBJD2D.mjs";
5
+ } from "./chunk-WZTY77GY.mjs";
8
6
  import {
9
- ConfigSchema,
10
7
  SprintSchema,
11
8
  TasksSchema,
12
9
  appendToFile,
@@ -14,7 +11,6 @@ import {
14
11
  ensureDir,
15
12
  fileExists,
16
13
  generateSprintId,
17
- getConfigPath,
18
14
  getProgressFilePath,
19
15
  getSprintDir,
20
16
  getSprintFilePath,
@@ -25,7 +21,7 @@ import {
25
21
  readValidatedJson,
26
22
  removeDir,
27
23
  writeValidatedJson
28
- } from "./chunk-CTP2A436.mjs";
24
+ } from "./chunk-D2HWXEHH.mjs";
29
25
  import {
30
26
  LockError,
31
27
  NoCurrentSprintError,
@@ -34,60 +30,6 @@ import {
34
30
  StorageError
35
31
  } from "./chunk-57UWLHRH.mjs";
36
32
 
37
- // src/integration/persistence/config.ts
38
- var DEFAULT_EVALUATION_ITERATIONS = 1;
39
- var DEFAULT_CONFIG = {
40
- currentSprint: null,
41
- aiProvider: null,
42
- editor: null
43
- };
44
- async function getConfig() {
45
- const configPath = getConfigPath();
46
- if (!await fileExists(configPath)) {
47
- return DEFAULT_CONFIG;
48
- }
49
- return unwrapOrThrow(await readValidatedJson(configPath, ConfigSchema));
50
- }
51
- async function saveConfig(config) {
52
- unwrapOrThrow(await writeValidatedJson(getConfigPath(), config, ConfigSchema));
53
- }
54
- async function getCurrentSprint() {
55
- const config = await getConfig();
56
- return config.currentSprint;
57
- }
58
- async function setCurrentSprint(sprintId) {
59
- const config = await getConfig();
60
- config.currentSprint = sprintId;
61
- await saveConfig(config);
62
- }
63
- async function getAiProvider() {
64
- const config = await getConfig();
65
- return config.aiProvider ?? null;
66
- }
67
- async function setAiProvider(provider) {
68
- const config = await getConfig();
69
- config.aiProvider = provider;
70
- await saveConfig(config);
71
- }
72
- async function getEditor() {
73
- const config = await getConfig();
74
- return config.editor ?? null;
75
- }
76
- async function setEditor(editor) {
77
- const config = await getConfig();
78
- config.editor = editor;
79
- await saveConfig(config);
80
- }
81
- async function getEvaluationIterations() {
82
- const config = await getConfig();
83
- return config.evaluationIterations ?? DEFAULT_EVALUATION_ITERATIONS;
84
- }
85
- async function setEvaluationIterations(iterations) {
86
- const config = await getConfig();
87
- config.evaluationIterations = iterations;
88
- await saveConfig(config);
89
- }
90
-
91
33
  // src/integration/persistence/progress.ts
92
34
  import { execSync } from "child_process";
93
35
 
@@ -267,7 +209,7 @@ async function getProgress(sprintId) {
267
209
  const id = await resolveSprintId(sprintId);
268
210
  const result = await readTextFile(getProgressFilePath(id));
269
211
  if (!result.ok) {
270
- if (result.error instanceof StorageError && result.error.cause?.code === "ENOENT") {
212
+ if (result.error instanceof StorageError && result.error.cause && "code" in result.error.cause && result.error.cause.code === "ENOENT") {
271
213
  return "";
272
214
  }
273
215
  throw result.error;
@@ -481,16 +423,6 @@ async function resolveSprintId(sprintId) {
481
423
  }
482
424
 
483
425
  export {
484
- getConfig,
485
- saveConfig,
486
- getCurrentSprint,
487
- setCurrentSprint,
488
- getAiProvider,
489
- setAiProvider,
490
- getEditor,
491
- setEditor,
492
- getEvaluationIterations,
493
- setEvaluationIterations,
494
426
  withFileLock,
495
427
  logProgress,
496
428
  getProgress,
@@ -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-2FT37OZX.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-LDSG7G2T.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-WZTY77GY.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-D2HWXEHH.mjs";
33
39
  import {
34
40
  IOError,
35
- ProjectExistsError
41
+ ProjectExistsError,
42
+ ValidationError
36
43
  } from "./chunk-57UWLHRH.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
  };