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
@@ -2,27 +2,19 @@
2
2
  import {
3
3
  PromptCancelledError,
4
4
  addCheckScriptToRepository,
5
- escapableSelect
6
- } from "./chunk-D2YGPLIV.mjs";
5
+ escapableSelect,
6
+ getAllConfigSchemaEntries,
7
+ getConfigDefaultValue,
8
+ parseConfigValue
9
+ } from "./chunk-PYZEQ2VK.mjs";
7
10
  import {
8
11
  editorInput
9
- } from "./chunk-7JLZQICD.mjs";
12
+ } from "./chunk-OGEXYSFS.mjs";
10
13
  import {
11
- addProjectRepo,
12
- getProject,
13
- getProjectById,
14
- getRepoById,
15
- listProjects,
16
- removeProject,
17
- removeProjectRepo,
18
- resolveRepoPath
19
- } from "./chunk-NUYQK5MN.mjs";
20
- import {
21
- SignalParser,
22
14
  addTask,
23
15
  areAllTasksDone,
24
16
  branchExists,
25
- buildTicketRefinePrompt,
17
+ createExecuteSprintPipeline,
26
18
  createIdeatePipeline,
27
19
  createPlanPipeline,
28
20
  createRefinePipeline,
@@ -40,19 +32,23 @@ import {
40
32
  isGlabAvailable,
41
33
  listTasks,
42
34
  parseRequirementsFile,
43
- processLifecycleAdapter,
44
- providerDisplayName,
45
35
  removeTask,
46
36
  renderParsedTasksTable,
47
37
  reorderByDependencies,
48
38
  reorderTask,
49
- resolveProvider,
50
39
  runAiSession,
51
40
  saveTasks,
52
41
  updateTask,
53
42
  updateTaskStatus,
54
43
  validateImportTasks
55
- } from "./chunk-TKPTT2UG.mjs";
44
+ } from "./chunk-OFILN7QL.mjs";
45
+ import {
46
+ SignalParser,
47
+ buildTicketRefinePrompt,
48
+ processLifecycleAdapter,
49
+ providerDisplayName,
50
+ resolveProvider
51
+ } from "./chunk-ZLWSPLWI.mjs";
56
52
  import {
57
53
  fetchIssueFromUrl,
58
54
  formatIssueContext,
@@ -63,7 +59,7 @@ import {
63
59
  removeTicket,
64
60
  truncate,
65
61
  updateTicket
66
- } from "./chunk-JOQO4HMM.mjs";
62
+ } from "./chunk-GQ2WFKBN.mjs";
67
63
  import {
68
64
  EXIT_ERROR,
69
65
  exitWithCode
@@ -72,33 +68,33 @@ import {
72
68
  getPrompt,
73
69
  getSharedDeps
74
70
  } from "./chunk-747KW2RW.mjs";
71
+ import {
72
+ addProjectRepo,
73
+ getProject,
74
+ getProjectById,
75
+ getRepoById,
76
+ listProjects,
77
+ removeProject,
78
+ removeProjectRepo,
79
+ resolveRepoPath
80
+ } from "./chunk-BSB4EDGR.mjs";
75
81
  import {
76
82
  activateSprint,
77
83
  assertSprintStatus,
78
84
  closeSprint,
79
85
  createSprint,
80
86
  deleteSprint,
81
- getAiProvider,
82
- getConfig,
83
- getCurrentSprint,
84
87
  getCurrentSprintOrThrow,
85
- getEditor,
86
- getEvaluationIterations,
87
88
  getProgress,
88
89
  getSprint,
89
90
  listSprints,
90
91
  logProgress,
91
92
  logSprintBaselines,
92
93
  resolveSprintId,
93
- saveConfig,
94
94
  saveSprint,
95
- setAiProvider,
96
- setCurrentSprint,
97
- setEditor,
98
- setEvaluationIterations,
99
95
  summarizeProgressForContext,
100
96
  withFileLock
101
- } from "./chunk-YCDUVPRT.mjs";
97
+ } from "./chunk-CBMFRQ4Y.mjs";
102
98
  import {
103
99
  DETAIL_LABEL_WIDTH,
104
100
  badge,
@@ -111,6 +107,11 @@ import {
111
107
  fieldMultiline,
112
108
  formatSprintStatus,
113
109
  formatTaskStatus,
110
+ getAiProvider,
111
+ getConfig,
112
+ getCurrentSprint,
113
+ getEditor,
114
+ getEvaluationIterations,
114
115
  getQuoteForContext,
115
116
  horizontalLine,
116
117
  icons,
@@ -123,6 +124,11 @@ import {
123
124
  progressBar,
124
125
  renderCard,
125
126
  renderTable,
127
+ saveConfig,
128
+ setAiProvider,
129
+ setCurrentSprint,
130
+ setEditor,
131
+ setEvaluationIterations,
126
132
  showEmpty,
127
133
  showError,
128
134
  showNextStep,
@@ -133,13 +139,14 @@ import {
133
139
  showWarning,
134
140
  success,
135
141
  terminalBell
136
- } from "./chunk-FKMKOWLA.mjs";
142
+ } from "./chunk-XN2UIHBY.mjs";
137
143
  import {
138
144
  ensureError,
139
145
  unwrapOrThrow,
140
146
  wrapAsync
141
147
  } from "./chunk-IWXBJD2D.mjs";
142
148
  import {
149
+ CURRENT_ONBOARDING_VERSION,
143
150
  ImportTasksSchema,
144
151
  RequirementStatusSchema,
145
152
  SprintSchema,
@@ -162,22 +169,22 @@ import {
162
169
  getTasksFilePath,
163
170
  readValidatedJson,
164
171
  validateProjectPath
165
- } from "./chunk-CTP2A436.mjs";
172
+ } from "./chunk-WDMLPXOD.mjs";
166
173
  import {
174
+ ExecutionAlreadyRunningError,
167
175
  NoCurrentSprintError,
168
176
  ProjectNotFoundError,
169
177
  SprintNotFoundError,
170
178
  SprintStatusError,
171
179
  StorageError,
172
180
  TaskNotFoundError,
173
- TicketNotFoundError,
174
- ValidationError
175
- } from "./chunk-57UWLHRH.mjs";
181
+ TicketNotFoundError
182
+ } from "./chunk-VAZ3LJBI.mjs";
176
183
 
177
184
  // package.json
178
185
  var package_default = {
179
186
  name: "ralphctl",
180
- version: "0.4.2",
187
+ version: "0.4.4",
181
188
  description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
182
189
  homepage: "https://github.com/lukas-grigis/ralphctl",
183
190
  type: "module",
@@ -245,8 +252,8 @@ var package_default = {
245
252
  "@types/node": "^25.6.0",
246
253
  "@types/react": "^19.2.14",
247
254
  "@types/tabtab": "^3.0.4",
248
- "@vitest/coverage-v8": "^4.1.4",
249
- eslint: "^10.2.0",
255
+ "@vitest/coverage-v8": "^4.1.5",
256
+ eslint: "^10.2.1",
250
257
  "eslint-config-prettier": "^10.1.8",
251
258
  globals: "^17.5.0",
252
259
  husky: "^9.1.7",
@@ -256,11 +263,11 @@ var package_default = {
256
263
  tsup: "^8.5.1",
257
264
  tsx: "^4.21.0",
258
265
  typescript: "^5.9.3",
259
- "typescript-eslint": "^8.58.2",
260
- vitest: "^4.1.4"
266
+ "typescript-eslint": "^8.59.0",
267
+ vitest: "^4.1.5"
261
268
  },
262
269
  "lint-staged": {
263
- "*.ts": [
270
+ "*.{ts,tsx}": [
264
271
  "eslint --cache --fix",
265
272
  "prettier --write"
266
273
  ],
@@ -1236,6 +1243,153 @@ var InkPromptAdapter = class {
1236
1243
  }
1237
1244
  };
1238
1245
 
1246
+ // src/integration/ui/tui/runtime/event-bus.ts
1247
+ var FRAME_MS2 = 16;
1248
+ var InMemoryLogEventBus = class {
1249
+ listeners = /* @__PURE__ */ new Set();
1250
+ buffer = [];
1251
+ flushTimer = null;
1252
+ emit(event) {
1253
+ this.buffer.push(event);
1254
+ this.scheduleFlush();
1255
+ }
1256
+ subscribe(listener) {
1257
+ this.listeners.add(listener);
1258
+ return () => {
1259
+ this.listeners.delete(listener);
1260
+ };
1261
+ }
1262
+ dispose() {
1263
+ if (this.flushTimer) {
1264
+ clearTimeout(this.flushTimer);
1265
+ this.flushTimer = null;
1266
+ }
1267
+ this.listeners.clear();
1268
+ this.buffer.length = 0;
1269
+ }
1270
+ /** Force immediate drain. Primarily for tests. */
1271
+ flush() {
1272
+ if (this.flushTimer) {
1273
+ clearTimeout(this.flushTimer);
1274
+ this.flushTimer = null;
1275
+ }
1276
+ this.drain();
1277
+ }
1278
+ scheduleFlush() {
1279
+ if (this.flushTimer !== null) return;
1280
+ const timer = setTimeout(() => {
1281
+ this.flushTimer = null;
1282
+ this.drain();
1283
+ }, FRAME_MS2);
1284
+ timer.unref();
1285
+ this.flushTimer = timer;
1286
+ }
1287
+ drain() {
1288
+ if (this.buffer.length === 0) return;
1289
+ const batch = this.buffer.splice(0, this.buffer.length);
1290
+ for (const listener of this.listeners) {
1291
+ try {
1292
+ listener(batch);
1293
+ } catch {
1294
+ }
1295
+ }
1296
+ }
1297
+ };
1298
+ var logEventBus = new InMemoryLogEventBus();
1299
+
1300
+ // src/integration/logging/ink-sink.ts
1301
+ var spinnerId = 0;
1302
+ var InkSink = class _InkSink {
1303
+ constructor(context = {}, bus) {
1304
+ this.context = context;
1305
+ this.bus = bus ?? logEventBus;
1306
+ }
1307
+ context;
1308
+ bus;
1309
+ emit(event) {
1310
+ this.bus.emit(event);
1311
+ }
1312
+ // -- Structured log levels --------------------------------------------------
1313
+ debug(message, context) {
1314
+ this.emit({ kind: "log", level: "debug", message, context: this.merge(context), timestamp: /* @__PURE__ */ new Date() });
1315
+ }
1316
+ info(message, context) {
1317
+ this.emit({ kind: "log", level: "info", message, context: this.merge(context), timestamp: /* @__PURE__ */ new Date() });
1318
+ }
1319
+ warn(message, context) {
1320
+ this.emit({ kind: "log", level: "warn", message, context: this.merge(context), timestamp: /* @__PURE__ */ new Date() });
1321
+ }
1322
+ error(message, context) {
1323
+ this.emit({ kind: "log", level: "error", message, context: this.merge(context), timestamp: /* @__PURE__ */ new Date() });
1324
+ }
1325
+ // -- UI-level output --------------------------------------------------------
1326
+ success(message) {
1327
+ this.emit({ kind: "log", level: "success", message, context: this.context, timestamp: /* @__PURE__ */ new Date() });
1328
+ }
1329
+ warning(message) {
1330
+ this.emit({ kind: "log", level: "warning", message, context: this.context, timestamp: /* @__PURE__ */ new Date() });
1331
+ }
1332
+ tip(message) {
1333
+ this.emit({ kind: "log", level: "tip", message, context: this.context, timestamp: /* @__PURE__ */ new Date() });
1334
+ }
1335
+ // -- Layout -----------------------------------------------------------------
1336
+ header(title, icon) {
1337
+ this.emit({ kind: "header", title, icon, timestamp: /* @__PURE__ */ new Date() });
1338
+ }
1339
+ separator(width = 40) {
1340
+ this.emit({ kind: "separator", width, timestamp: /* @__PURE__ */ new Date() });
1341
+ }
1342
+ field(label, value, _width) {
1343
+ void _width;
1344
+ this.emit({ kind: "field", label, value, timestamp: /* @__PURE__ */ new Date() });
1345
+ }
1346
+ card(title, lines) {
1347
+ this.emit({ kind: "card", title, lines, timestamp: /* @__PURE__ */ new Date() });
1348
+ }
1349
+ newline() {
1350
+ this.emit({ kind: "newline", timestamp: /* @__PURE__ */ new Date() });
1351
+ }
1352
+ dim(message) {
1353
+ this.emit({ kind: "log", level: "dim", message, context: this.context, timestamp: /* @__PURE__ */ new Date() });
1354
+ }
1355
+ item(message) {
1356
+ this.emit({ kind: "log", level: "item", message, context: this.context, timestamp: /* @__PURE__ */ new Date() });
1357
+ }
1358
+ // -- Interactive ------------------------------------------------------------
1359
+ spinner(message) {
1360
+ const id = ++spinnerId;
1361
+ this.emit({ kind: "spinner-start", id, message, timestamp: /* @__PURE__ */ new Date() });
1362
+ return {
1363
+ succeed: (msg) => {
1364
+ this.emit({ kind: "spinner-succeed", id, message: msg, timestamp: /* @__PURE__ */ new Date() });
1365
+ },
1366
+ fail: (msg) => {
1367
+ this.emit({ kind: "spinner-fail", id, message: msg, timestamp: /* @__PURE__ */ new Date() });
1368
+ },
1369
+ stop: () => {
1370
+ this.emit({ kind: "spinner-stop", id, timestamp: /* @__PURE__ */ new Date() });
1371
+ }
1372
+ };
1373
+ }
1374
+ // -- Scoped child -----------------------------------------------------------
1375
+ child(context) {
1376
+ return new _InkSink({ ...this.context, ...context }, this.bus);
1377
+ }
1378
+ // -- Timing -----------------------------------------------------------------
1379
+ time(label) {
1380
+ const start = Date.now();
1381
+ return () => {
1382
+ const ms = Date.now() - start;
1383
+ this.debug(`${label}: ${String(ms)}ms`);
1384
+ };
1385
+ }
1386
+ // -- Internals --------------------------------------------------------------
1387
+ merge(extra) {
1388
+ if (!extra) return this.context;
1389
+ return { ...this.context, ...extra };
1390
+ }
1391
+ };
1392
+
1239
1393
  // src/integration/persistence/evaluation.ts
1240
1394
  async function writeEvaluation(sprintId, taskId, iteration, status, body) {
1241
1395
  const filePath = getEvaluationFilePath(sprintId, taskId);
@@ -1835,6 +1989,148 @@ var RateLimitCoordinator = class {
1835
1989
  }
1836
1990
  };
1837
1991
 
1992
+ // src/integration/runtime/execution-registry.ts
1993
+ import { randomUUID } from "crypto";
1994
+
1995
+ // src/integration/runtime/execution-scope.ts
1996
+ function createExecutionScope(baseShared, args) {
1997
+ const scopedLogger = baseShared.logger instanceof InkSink ? new InkSink({ executionId: args.executionId }, args.logEventBus) : baseShared.logger.child({ executionId: args.executionId });
1998
+ return {
1999
+ ...baseShared,
2000
+ signalBus: args.signalBus,
2001
+ logger: scopedLogger
2002
+ };
2003
+ }
2004
+
2005
+ // src/integration/runtime/execution-registry.ts
2006
+ var InMemoryExecutionRegistry = class {
2007
+ baseShared;
2008
+ runner;
2009
+ generateId;
2010
+ createAbortController;
2011
+ entries = /* @__PURE__ */ new Map();
2012
+ listeners = /* @__PURE__ */ new Set();
2013
+ constructor(options) {
2014
+ this.baseShared = options.baseShared;
2015
+ this.runner = options.runner;
2016
+ this.generateId = options.generateId ?? (() => randomUUID());
2017
+ this.createAbortController = options.createAbortController ?? (() => new AbortController());
2018
+ }
2019
+ async start(params) {
2020
+ const sprint = await this.baseShared.persistence.getSprint(params.sprintId);
2021
+ const project = await this.baseShared.persistence.getProjectById(sprint.projectId);
2022
+ const existing = this.findRunningForProject(project.name);
2023
+ if (existing) {
2024
+ throw new ExecutionAlreadyRunningError(project.name, existing.id);
2025
+ }
2026
+ const executionId = this.generateId();
2027
+ const signalBus = new InMemorySignalBus();
2028
+ const logEventBus2 = new InMemoryLogEventBus();
2029
+ const abortController = this.createAbortController();
2030
+ const scopedShared = createExecutionScope(this.baseShared, {
2031
+ executionId,
2032
+ logEventBus: logEventBus2,
2033
+ signalBus,
2034
+ abortController
2035
+ });
2036
+ const execution = {
2037
+ id: executionId,
2038
+ projectName: project.name,
2039
+ sprintId: sprint.id,
2040
+ sprint,
2041
+ status: "running",
2042
+ startedAt: /* @__PURE__ */ new Date()
2043
+ };
2044
+ const entry = {
2045
+ execution,
2046
+ signalBus,
2047
+ logEventBus: logEventBus2,
2048
+ abortController,
2049
+ // Lazy-initialised below; `runner` needs the entry to exist in the map
2050
+ // before it starts emitting so listeners can observe early signals.
2051
+ pipelinePromise: Promise.resolve()
2052
+ };
2053
+ this.entries.set(executionId, entry);
2054
+ this.notify(execution);
2055
+ entry.pipelinePromise = this.runPipeline(executionId, scopedShared, params, abortController.signal);
2056
+ return execution;
2057
+ }
2058
+ async runPipeline(executionId, scopedShared, params, abortSignal) {
2059
+ try {
2060
+ const summary = await this.runner(scopedShared, {
2061
+ sprintId: params.sprintId,
2062
+ options: params.options,
2063
+ abortSignal
2064
+ });
2065
+ if (abortSignal.aborted) {
2066
+ this.transition(executionId, "cancelled", summary ?? void 0);
2067
+ } else {
2068
+ this.transition(executionId, "completed", summary ?? void 0);
2069
+ }
2070
+ } catch (err) {
2071
+ void err;
2072
+ if (abortSignal.aborted) {
2073
+ this.transition(executionId, "cancelled");
2074
+ } else {
2075
+ this.transition(executionId, "failed");
2076
+ }
2077
+ }
2078
+ }
2079
+ get(id) {
2080
+ return this.entries.get(id)?.execution ?? null;
2081
+ }
2082
+ list() {
2083
+ return Array.from(this.entries.values(), (entry) => entry.execution);
2084
+ }
2085
+ cancel(id) {
2086
+ const entry = this.entries.get(id);
2087
+ if (!entry) return;
2088
+ if (entry.execution.status !== "running") return;
2089
+ entry.abortController.abort();
2090
+ }
2091
+ subscribe(listener) {
2092
+ this.listeners.add(listener);
2093
+ return () => {
2094
+ this.listeners.delete(listener);
2095
+ };
2096
+ }
2097
+ getSignalBus(id) {
2098
+ return this.entries.get(id)?.signalBus ?? null;
2099
+ }
2100
+ getLogEventBus(id) {
2101
+ return this.entries.get(id)?.logEventBus ?? null;
2102
+ }
2103
+ transition(executionId, status, summary) {
2104
+ const entry = this.entries.get(executionId);
2105
+ if (!entry) return;
2106
+ if (entry.execution.status === status) return;
2107
+ const next = {
2108
+ ...entry.execution,
2109
+ status,
2110
+ endedAt: /* @__PURE__ */ new Date(),
2111
+ summary: summary ?? entry.execution.summary
2112
+ };
2113
+ entry.execution = next;
2114
+ this.notify(next);
2115
+ }
2116
+ findRunningForProject(projectName) {
2117
+ for (const entry of this.entries.values()) {
2118
+ if (entry.execution.projectName === projectName && entry.execution.status === "running") {
2119
+ return entry.execution;
2120
+ }
2121
+ }
2122
+ return null;
2123
+ }
2124
+ notify(execution) {
2125
+ for (const listener of this.listeners) {
2126
+ try {
2127
+ listener(execution);
2128
+ } catch {
2129
+ }
2130
+ }
2131
+ }
2132
+ };
2133
+
1838
2134
  // src/application/shared.ts
1839
2135
  function createSharedDeps(overrides = {}) {
1840
2136
  const persistence = overrides.persistence ?? new FilePersistenceAdapter();
@@ -1846,7 +2142,7 @@ function createSharedDeps(overrides = {}) {
1846
2142
  const signalBus = overrides.signalBus ?? new NoopSignalBus();
1847
2143
  const createRateLimitCoordinator = overrides.createRateLimitCoordinator ?? (() => new RateLimitCoordinator());
1848
2144
  const processLifecycle = overrides.processLifecycle ?? processLifecycleAdapter;
1849
- return {
2145
+ const base = {
1850
2146
  persistence,
1851
2147
  filesystem,
1852
2148
  signalParser,
@@ -1857,6 +2153,17 @@ function createSharedDeps(overrides = {}) {
1857
2153
  createRateLimitCoordinator,
1858
2154
  processLifecycle
1859
2155
  };
2156
+ const defaultRunner = async (scopedShared, { sprintId, options, abortSignal }) => {
2157
+ const pipeline = createExecuteSprintPipeline(scopedShared, options);
2158
+ const result = await executePipeline(pipeline, { sprintId, abortSignal });
2159
+ if (!result.ok) {
2160
+ throw result.error;
2161
+ }
2162
+ const summary = result.value.context.executionSummary;
2163
+ return summary ?? null;
2164
+ };
2165
+ base.executionRegistry = overrides.executionRegistry ?? new InMemoryExecutionRegistry({ baseShared: base, runner: defaultRunner });
2166
+ return base;
1860
2167
  }
1861
2168
 
1862
2169
  // src/integration/cli/commands/project/list.ts
@@ -1897,7 +2204,7 @@ async function selectProject(message = "Select project:") {
1897
2204
  default: true
1898
2205
  });
1899
2206
  if (create) {
1900
- const { projectAddCommand } = await import("./add-GX7P7XTT.mjs");
2207
+ const { projectAddCommand } = await import("./add-YVXM34RP.mjs");
1901
2208
  await projectAddCommand({ interactive: true });
1902
2209
  const updated = await listProjects();
1903
2210
  if (updated.length === 0) return null;
@@ -1932,7 +2239,7 @@ async function selectSprint(message = "Select sprint:", filter) {
1932
2239
  default: true
1933
2240
  });
1934
2241
  if (create) {
1935
- const { sprintCreateCommand } = await import("./create-7WFSCMP4.mjs");
2242
+ const { sprintCreateCommand } = await import("./create-Z635FQKO.mjs");
1936
2243
  await sprintCreateCommand({ interactive: true });
1937
2244
  const updated = await listSprints();
1938
2245
  const refiltered = filter ? updated.filter((s) => filter.includes(s.status)) : updated;
@@ -1967,7 +2274,7 @@ async function selectTicket(message = "Select ticket:", filter) {
1967
2274
  default: true
1968
2275
  });
1969
2276
  if (create) {
1970
- const { ticketAddCommand } = await import("./add-CIM72NE3.mjs");
2277
+ const { ticketAddCommand } = await import("./add-DVPVHENV.mjs");
1971
2278
  await ticketAddCommand({ interactive: true });
1972
2279
  const updated = await listTickets();
1973
2280
  const refiltered = filter ? updated.filter(filter) : updated;
@@ -4615,136 +4922,6 @@ async function progressShowCommand() {
4615
4922
  console.log(content);
4616
4923
  }
4617
4924
 
4618
- // src/integration/config/schema-provider.ts
4619
- import { Result as Result6 } from "typescript-result";
4620
-
4621
- // src/domain/config-schema.ts
4622
- var ConfigSchemaDefinition = {
4623
- currentSprint: {
4624
- key: "currentSprint",
4625
- label: "Current Sprint",
4626
- type: "string",
4627
- default: null,
4628
- description: "Currently active sprint ID (set by `sprint start`, cleared on `sprint close`)",
4629
- validation: (val) => val === null || typeof val === "string" && val.length > 0,
4630
- scope: "global"
4631
- },
4632
- aiProvider: {
4633
- key: "aiProvider",
4634
- label: "AI Provider",
4635
- type: "enum",
4636
- enum: ["claude", "copilot"],
4637
- default: null,
4638
- description: "AI provider for task execution (Claude Code or GitHub Copilot CLI)",
4639
- validation: (val) => val === null || typeof val === "string" && ["claude", "copilot"].includes(val),
4640
- scope: "global"
4641
- },
4642
- evaluationIterations: {
4643
- key: "evaluationIterations",
4644
- label: "Evaluation Iterations",
4645
- type: "integer",
4646
- min: 0,
4647
- max: 10,
4648
- default: 1,
4649
- description: "Number of fix-attempt iterations after initial evaluation; 0 = disabled. Higher values allow more refinement rounds.",
4650
- validation: (val) => typeof val === "number" && Number.isInteger(val) && val >= 0 && val <= 10,
4651
- scope: "sprint"
4652
- }
4653
- // Phase 2+ additions can be added here as single schema entries
4654
- // maxTaskTurns: { ... },
4655
- // checkScriptTimeout: { ... },
4656
- };
4657
- function getSchemaEntry(key) {
4658
- return ConfigSchemaDefinition[key];
4659
- }
4660
- function getAllSchemaEntries() {
4661
- return Object.values(ConfigSchemaDefinition);
4662
- }
4663
- function getDefaultValue(key) {
4664
- return ConfigSchemaDefinition[key].default;
4665
- }
4666
-
4667
- // src/integration/config/schema-provider.ts
4668
- function getAllConfigSchemaEntries() {
4669
- return getAllSchemaEntries();
4670
- }
4671
- function getConfigSchemaEntry(key) {
4672
- return getSchemaEntry(key);
4673
- }
4674
- function getConfigDefaultValue(key) {
4675
- return getDefaultValue(key);
4676
- }
4677
- function validateConfigValue(key, value) {
4678
- if (!(key in ConfigSchemaDefinition)) {
4679
- return Result6.error(new ValidationError(`Unknown config key: ${key}`, key));
4680
- }
4681
- const schemaKey = key;
4682
- const entry = getConfigSchemaEntry(schemaKey);
4683
- if (!entry.validation(value)) {
4684
- let errorMsg = `Invalid value for ${key}`;
4685
- if (entry.type === "enum" && entry.enum) {
4686
- errorMsg += `: must be one of ${entry.enum.join(", ")}`;
4687
- } else if (entry.type === "integer" || entry.type === "number") {
4688
- const constraints = [];
4689
- if (entry.min !== void 0) constraints.push(`min: ${String(entry.min)}`);
4690
- if (entry.max !== void 0) constraints.push(`max: ${String(entry.max)}`);
4691
- if (constraints.length > 0) errorMsg += ` (${constraints.join(", ")})`;
4692
- }
4693
- return Result6.error(new ValidationError(errorMsg, key));
4694
- }
4695
- return Result6.ok(value);
4696
- }
4697
- function parseConfigValue(key, stringValue) {
4698
- if (!(key in ConfigSchemaDefinition)) {
4699
- return Result6.error(new ValidationError(`Unknown config key: ${key}`, key));
4700
- }
4701
- const schemaKey = key;
4702
- const entry = getConfigSchemaEntry(schemaKey);
4703
- let parsedValue;
4704
- try {
4705
- switch (entry.type) {
4706
- case "string":
4707
- parsedValue = stringValue === "null" ? null : stringValue;
4708
- break;
4709
- case "integer":
4710
- parsedValue = stringValue === "null" ? null : Number.parseInt(stringValue, 10);
4711
- if (Number.isNaN(parsedValue)) {
4712
- return Result6.error(new ValidationError(`Expected integer for ${key}, got ${stringValue}`, key));
4713
- }
4714
- break;
4715
- case "number":
4716
- parsedValue = stringValue === "null" ? null : Number.parseFloat(stringValue);
4717
- if (Number.isNaN(parsedValue)) {
4718
- return Result6.error(new ValidationError(`Expected number for ${key}, got ${stringValue}`, key));
4719
- }
4720
- break;
4721
- case "boolean":
4722
- if (stringValue.toLowerCase() === "true" || stringValue === "1") {
4723
- parsedValue = true;
4724
- } else if (stringValue.toLowerCase() === "false" || stringValue === "0") {
4725
- parsedValue = false;
4726
- } else if (stringValue === "null") {
4727
- parsedValue = null;
4728
- } else {
4729
- return Result6.error(new ValidationError(`Expected boolean for ${key}, got ${stringValue}`, key));
4730
- }
4731
- break;
4732
- case "enum":
4733
- if (stringValue === "null") {
4734
- parsedValue = null;
4735
- } else {
4736
- parsedValue = stringValue;
4737
- }
4738
- break;
4739
- }
4740
- } catch (err) {
4741
- return Result6.error(
4742
- new ValidationError(`Failed to parse ${key}: ${err instanceof Error ? err.message : String(err)}`, key)
4743
- );
4744
- }
4745
- return validateConfigValue(key, parsedValue);
4746
- }
4747
-
4748
4925
  // src/integration/cli/commands/config/config.ts
4749
4926
  var KEY_ALIASES = {
4750
4927
  provider: "aiProvider"
@@ -4820,7 +4997,7 @@ async function configShowCommand() {
4820
4997
  }
4821
4998
 
4822
4999
  // src/integration/cli/commands/doctor/doctor.ts
4823
- import { access as access2, constants } from "fs/promises";
5000
+ import { access as access2, constants, readFile as readFile4 } from "fs/promises";
4824
5001
  import { join as join5 } from "path";
4825
5002
  import { spawnSync as spawnSync2 } from "child_process";
4826
5003
  var REQUIRED_NODE_MAJOR = 24;
@@ -4935,6 +5112,90 @@ async function checkProjectPaths() {
4935
5112
  }
4936
5113
  return { name: "Project paths", status: "fail", detail: issues.join("; ") };
4937
5114
  }
5115
+ function providerInstructionsRelPath(provider) {
5116
+ if (provider === "claude") return "CLAUDE.md";
5117
+ return ".github/copilot-instructions.md";
5118
+ }
5119
+ async function checkRepoOnboarding() {
5120
+ const projects = await listProjects();
5121
+ const config = await getConfig();
5122
+ const provider = config.aiProvider;
5123
+ const results = [];
5124
+ for (const project of projects) {
5125
+ for (const repo of project.repositories) {
5126
+ const name = `Onboarding \u2014 ${project.name}/${repo.name}`;
5127
+ if (!provider) {
5128
+ results.push({
5129
+ name,
5130
+ status: "skip",
5131
+ detail: "configure an AI provider to use onboarding"
5132
+ });
5133
+ continue;
5134
+ }
5135
+ const relPath = providerInstructionsRelPath(provider);
5136
+ const instructionsPath = join5(repo.path, relPath);
5137
+ const hasInstructions = await fileExists(instructionsPath);
5138
+ const ver = repo.onboardingVersion;
5139
+ if (ver == null) {
5140
+ if (hasInstructions) {
5141
+ results.push({
5142
+ name,
5143
+ status: "pass",
5144
+ detail: `authored ${relPath} (not harness-managed)`
5145
+ });
5146
+ } else {
5147
+ results.push({
5148
+ name,
5149
+ status: "skip",
5150
+ detail: `never onboarded \u2014 run \`ralphctl project onboard ${project.name} --repo ${repo.name}\``
5151
+ });
5152
+ }
5153
+ continue;
5154
+ }
5155
+ if (!hasInstructions) {
5156
+ results.push({
5157
+ name,
5158
+ status: "warn",
5159
+ detail: `onboardingVersion set but ${relPath} missing \u2014 re-run \`ralphctl project onboard\``
5160
+ });
5161
+ continue;
5162
+ }
5163
+ if (ver < CURRENT_ONBOARDING_VERSION) {
5164
+ results.push({
5165
+ name,
5166
+ status: "warn",
5167
+ detail: `onboarding v${String(ver)} < v${String(CURRENT_ONBOARDING_VERSION)} \u2014 re-run to refresh`
5168
+ });
5169
+ continue;
5170
+ }
5171
+ if (ver > CURRENT_ONBOARDING_VERSION) {
5172
+ results.push({
5173
+ name,
5174
+ status: "warn",
5175
+ detail: `onboarding v${String(ver)} > v${String(CURRENT_ONBOARDING_VERSION)} \u2014 repo onboarded by a newer ralphctl version \u2014 upgrade the CLI to manage it cleanly`
5176
+ });
5177
+ continue;
5178
+ }
5179
+ let body;
5180
+ try {
5181
+ body = await readFile4(instructionsPath, "utf-8");
5182
+ } catch {
5183
+ results.push({ name, status: "warn", detail: `${relPath} unreadable \u2014 re-run \`ralphctl project onboard\`` });
5184
+ continue;
5185
+ }
5186
+ if (body.includes("LOW-CONFIDENCE:")) {
5187
+ results.push({
5188
+ name,
5189
+ status: "warn",
5190
+ detail: "low-confidence sections detected \u2014 re-run interactively to review"
5191
+ });
5192
+ continue;
5193
+ }
5194
+ results.push({ name, status: "pass", detail: `onboarding v${String(ver)}` });
5195
+ }
5196
+ }
5197
+ return results;
5198
+ }
4938
5199
  async function checkEvaluationConfig() {
4939
5200
  const config = await getConfig();
4940
5201
  if (config.evaluationIterations == null) {
@@ -4950,6 +5211,17 @@ async function checkEvaluationConfig() {
4950
5211
  detail: `evaluationIterations: ${String(config.evaluationIterations)}`
4951
5212
  };
4952
5213
  }
5214
+ async function checkAiCheckScriptDiscovery() {
5215
+ const config = await getConfig();
5216
+ if (config.aiCheckScriptDiscovery === false) {
5217
+ return {
5218
+ name: "AI check-script discovery",
5219
+ status: "warn",
5220
+ detail: "disabled \u2014 `project add` will skip the AI fallback for unrecognized ecosystems (re-enable: ralphctl config set aiCheckScriptDiscovery true)"
5221
+ };
5222
+ }
5223
+ return null;
5224
+ }
4953
5225
  async function checkConfigSchemaValidation() {
4954
5226
  const config = await getConfig();
4955
5227
  const entries = getAllConfigSchemaEntries();
@@ -4999,9 +5271,14 @@ async function doctorCommand() {
4999
5271
  checkProjectPaths(),
5000
5272
  checkCurrentSprint(),
5001
5273
  checkEvaluationConfig(),
5274
+ checkAiCheckScriptDiscovery(),
5002
5275
  checkConfigSchemaValidation()
5003
5276
  ]);
5004
- results.push(...asyncResults);
5277
+ for (const r of asyncResults) {
5278
+ if (r !== null) results.push(r);
5279
+ }
5280
+ const onboardingResults = await checkRepoOnboarding();
5281
+ results.push(...onboardingResults);
5005
5282
  for (const result of results) {
5006
5283
  if (result.status === "pass") {
5007
5284
  log.success(`${result.name}${result.detail ? colors.muted(` \u2014 ${result.detail}`) : ""}`);
@@ -5225,9 +5502,6 @@ export {
5225
5502
  loadDashboardData,
5226
5503
  getNextAction,
5227
5504
  showDashboard,
5228
- getAllSchemaEntries,
5229
- validateConfigValue,
5230
- parseConfigValue,
5231
5505
  configSetCommand,
5232
5506
  configShowCommand,
5233
5507
  checkNodeVersion,
@@ -5237,6 +5511,7 @@ export {
5237
5511
  checkGlabInstalled,
5238
5512
  checkDataDirectory,
5239
5513
  checkProjectPaths,
5514
+ checkRepoOnboarding,
5240
5515
  checkEvaluationConfig,
5241
5516
  checkConfigSchemaValidation,
5242
5517
  checkCurrentSprint,
@@ -5251,5 +5526,7 @@ export {
5251
5526
  PromptHost,
5252
5527
  registerExternalHost,
5253
5528
  InkPromptAdapter,
5529
+ logEventBus,
5530
+ InkSink,
5254
5531
  createSharedDeps
5255
5532
  };