tarsk 0.4.22 → 0.4.24

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 (50) hide show
  1. package/dist/index.js +491 -200
  2. package/dist/public/assets/{account-view-pOQZWJfl.js → account-view-D5WVMsrD.js} +1 -1
  3. package/dist/public/assets/{alert-dialog-BeLER45U.js → alert-dialog-JOYww-on.js} +1 -1
  4. package/dist/public/assets/{api-Bagaj1rQ.js → api-XK7H2gI0.js} +1 -1
  5. package/dist/public/assets/{browser-tab-DmootUHa.js → browser-tab-xojjEFYj.js} +1 -1
  6. package/dist/public/assets/chat-input-container-fUw0AY1R.js +1 -0
  7. package/dist/public/assets/{context-menu-BlG_bTqN.js → context-menu-5lS4H_pS.js} +1 -1
  8. package/dist/public/assets/conversation-history-view-IVxKEbP_.js +1 -0
  9. package/dist/public/assets/{dialogs-config-Dwpv4SNp.js → dialogs-config-3Hr2GRdt.js} +2 -2
  10. package/dist/public/assets/{diff-view-B8Grak1p.js → diff-view-DWNDi8zT.js} +2 -2
  11. package/dist/public/assets/{explorer-tab-view-lFzzjVe8.js → explorer-tab-view-BtTXy75I.js} +2 -2
  12. package/dist/public/assets/explorer-tree-DdPiBm7Y.js +1 -0
  13. package/dist/public/assets/{explorer-view-DjN4dtgh.js → explorer-view-06kjYKL2.js} +1 -1
  14. package/dist/public/assets/history-view-Cyhu-ped.js +1 -0
  15. package/dist/public/assets/index-CoryOY5D.css +1 -0
  16. package/dist/public/assets/index-DJNTxm7E.js +50 -0
  17. package/dist/public/assets/onboarding-DzgFi5ix.js +1 -0
  18. package/dist/public/assets/onboarding-dialog-lFFrB1GZ.js +1 -0
  19. package/dist/public/assets/{page-toolbar-CBE5_3E3.js → page-toolbar-De9hbhUD.js} +1 -1
  20. package/dist/public/assets/{project-settings-view-B13GlkU6.js → project-settings-view-DF-F7P8K.js} +1 -1
  21. package/dist/public/assets/{provider-details-view-BQQKYvR8.js → provider-details-view-CbGbbmaI.js} +1 -1
  22. package/dist/public/assets/{providers-sidebar-C2RknYQn.js → providers-sidebar-Dc3Gk1we.js} +1 -1
  23. package/dist/public/assets/{react-vendor-CYvpNUsM.js → react-vendor-BX34Jw0L.js} +5 -5
  24. package/dist/public/assets/resizable-asBmgm9X.js +1 -0
  25. package/dist/public/assets/{run-stop-button-1ECLRujs.js → run-stop-button-MczHUMXL.js} +2 -2
  26. package/dist/public/assets/{settings-view-CxiVTevy.js → settings-view-CtiXQd-N.js} +2 -2
  27. package/dist/public/assets/{side-panel-container-B9Dn9qD2.js → side-panel-container-5txNmqNe.js} +2 -2
  28. package/dist/public/assets/{standard-list-item-BJWg4I9s.js → standard-list-item-DUKJ8HEG.js} +1 -1
  29. package/dist/public/assets/store-xK3nfJ3V.js +2 -0
  30. package/dist/public/assets/{tab-context-COCYkRNN.js → tab-context-D2auNMAI.js} +1 -1
  31. package/dist/public/assets/tabs-B7OXJglU.js +1 -0
  32. package/dist/public/assets/{terminal-panel-Cea0pLdX.js → terminal-panel-D3OZDoCd.js} +2 -2
  33. package/dist/public/assets/{textarea-C5IMo2ya.js → textarea-Dg4M60Yx.js} +1 -1
  34. package/dist/public/assets/todos-view-CzW8DtQ9.js +1 -0
  35. package/dist/public/assets/{use-toast-rlEtMnzM.js → use-toast-C3jyoS6i.js} +1 -1
  36. package/dist/public/assets/{utils-C3gmypZX.js → utils-4yrqj6yI.js} +1 -1
  37. package/dist/public/index.html +19 -19
  38. package/package.json +1 -1
  39. package/dist/public/assets/chat-input-container-CfXgimBQ.js +0 -1
  40. package/dist/public/assets/conversation-history-view-DjO711iz.js +0 -1
  41. package/dist/public/assets/explorer-tree-CyWyxQvY.js +0 -1
  42. package/dist/public/assets/history-view-BvAOaO8q.js +0 -1
  43. package/dist/public/assets/index-BPF49zet.css +0 -1
  44. package/dist/public/assets/index-C7dA1l3u.js +0 -50
  45. package/dist/public/assets/onboarding-dialog-DSmKEXk4.js +0 -1
  46. package/dist/public/assets/onboarding-vGz3IczM.js +0 -1
  47. package/dist/public/assets/resizable-C82J5y6K.js +0 -1
  48. package/dist/public/assets/store-Cn1lT5YJ.js +0 -2
  49. package/dist/public/assets/tabs-M7nUZf6O.js +0 -1
  50. package/dist/public/assets/todos-view-DDVfCfgF.js +0 -1
package/dist/index.js CHANGED
@@ -188,6 +188,7 @@ async function initializeSchema(db) {
188
188
  description TEXT NOT NULL,
189
189
  status TEXT NOT NULL DEFAULT 'pending',
190
190
  assignedTo TEXT NOT NULL DEFAULT 'agent',
191
+ passes INTEGER NOT NULL DEFAULT 0,
191
192
  createdAt TEXT NOT NULL,
192
193
  updatedAt TEXT NOT NULL,
193
194
  FOREIGN KEY (threadId) REFERENCES threads(id) ON DELETE CASCADE
@@ -638,6 +639,25 @@ async function runMigrations(db) {
638
639
  await db.execute(`ALTER TABLE projects ADD COLUMN testPrompt TEXT`);
639
640
  await db.execute(`ALTER TABLE projects ADD COLUMN reviewPrompt TEXT`);
640
641
  }
642
+ const todosInfo = await db.execute(`PRAGMA table_info(todos)`);
643
+ const hasTodoPasses = todosInfo.rows.some(
644
+ (col) => col.name === "passes"
645
+ );
646
+ if (!hasTodoPasses) {
647
+ console.log("[db] Running migration: Adding passes column to todos");
648
+ await db.execute(`ALTER TABLE todos ADD COLUMN passes INTEGER NOT NULL DEFAULT 0`);
649
+ }
650
+ const hasRalphMode = convInfo.rows.some(
651
+ (col) => col.name === "request_ralphMode"
652
+ );
653
+ if (!hasRalphMode) {
654
+ console.log(
655
+ "[db] Running migration: Adding request_ralphMode column to conversation_history"
656
+ );
657
+ await db.execute(
658
+ `ALTER TABLE conversation_history ADD COLUMN request_ralphMode INTEGER NOT NULL DEFAULT 0`
659
+ );
660
+ }
641
661
  const hasValidationScript = projectsInfo.rows.some(
642
662
  (col) => col.name === "validationScript"
643
663
  );
@@ -3179,11 +3199,14 @@ init_database();
3179
3199
  import { randomUUID } from "crypto";
3180
3200
  async function fetchAllTodos(db, threadId) {
3181
3201
  const result = await db.execute({
3182
- sql: `SELECT id, threadId, description, status, assignedTo, createdAt, updatedAt
3202
+ sql: `SELECT id, threadId, description, status, assignedTo, passes, createdAt, updatedAt
3183
3203
  FROM todos WHERE threadId = ? ORDER BY createdAt ASC`,
3184
3204
  args: [threadId]
3185
3205
  });
3186
- return result.rows;
3206
+ return result.rows.map((row) => ({
3207
+ ...row,
3208
+ passes: row.passes === 1
3209
+ }));
3187
3210
  }
3188
3211
  async function addTodos(threadId, descriptions, assignedTo = "agent") {
3189
3212
  const db = await getDatabase();
@@ -3192,8 +3215,8 @@ async function addTodos(threadId, descriptions, assignedTo = "agent") {
3192
3215
  const id = randomUUID();
3193
3216
  const ts = new Date(baseTime + i).toISOString();
3194
3217
  await db.execute({
3195
- sql: `INSERT INTO todos (id, threadId, description, status, assignedTo, createdAt, updatedAt)
3196
- VALUES (?, ?, ?, 'pending', ?, ?, ?)`,
3218
+ sql: `INSERT INTO todos (id, threadId, description, status, assignedTo, passes, createdAt, updatedAt)
3219
+ VALUES (?, ?, ?, 'pending', ?, 0, ?, ?)`,
3197
3220
  args: [id, threadId, descriptions[i].trim(), assignedTo, ts, ts]
3198
3221
  });
3199
3222
  }
@@ -3206,13 +3229,71 @@ async function clearTodos(threadId) {
3206
3229
  args: [threadId]
3207
3230
  });
3208
3231
  }
3232
+ async function clearTodosIfAllDone(db, threadId) {
3233
+ const result = await db.execute({
3234
+ sql: `SELECT status FROM todos WHERE threadId = ?`,
3235
+ args: [threadId]
3236
+ });
3237
+ if (result.rows.length === 0) {
3238
+ return false;
3239
+ }
3240
+ const allDone = result.rows.every(
3241
+ (row) => row.status === "done"
3242
+ );
3243
+ if (!allDone) {
3244
+ return false;
3245
+ }
3246
+ await db.execute({
3247
+ sql: "DELETE FROM todos WHERE threadId = ?",
3248
+ args: [threadId]
3249
+ });
3250
+ return true;
3251
+ }
3252
+
3253
+ // src/features/ralph/ralph.progress.ts
3254
+ import { readFile as readFile3, appendFile, writeFile } from "fs/promises";
3255
+ import { join as join6 } from "path";
3256
+ var PROGRESS_FILE = "progress.txt";
3257
+ async function readProgress(threadPath) {
3258
+ try {
3259
+ return await readFile3(join6(threadPath, PROGRESS_FILE), "utf-8");
3260
+ } catch (err) {
3261
+ if (err.code === "ENOENT") {
3262
+ return "";
3263
+ }
3264
+ throw err;
3265
+ }
3266
+ }
3267
+ async function appendProgress(threadPath, learnings) {
3268
+ const filePath = join6(threadPath, PROGRESS_FILE);
3269
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3270
+ const entry = `
3271
+ --- ${timestamp} ---
3272
+ ${learnings}
3273
+ `;
3274
+ try {
3275
+ await appendFile(filePath, entry, "utf-8");
3276
+ } catch (err) {
3277
+ if (err.code === "ENOENT") {
3278
+ await writeFile(filePath, entry, "utf-8");
3279
+ } else {
3280
+ throw err;
3281
+ }
3282
+ }
3283
+ }
3209
3284
 
3210
3285
  // src/tools/todo.ts
3211
3286
  var todoSchema = Type12.Object({
3212
3287
  action: Type12.Union(
3213
- [Type12.Literal("add"), Type12.Literal("update"), Type12.Literal("list"), Type12.Literal("delete")],
3288
+ [
3289
+ Type12.Literal("add"),
3290
+ Type12.Literal("update"),
3291
+ Type12.Literal("list"),
3292
+ Type12.Literal("delete"),
3293
+ Type12.Literal("learnings")
3294
+ ],
3214
3295
  {
3215
- description: "Operation to perform: 'add' creates new items, 'update' changes status, 'list' returns all items, 'delete' removes items."
3296
+ description: "Operation to perform: 'add' creates new items, 'update' changes status, 'list' returns all items, 'delete' removes items, 'learnings' appends text to progress.txt for cross-iteration memory."
3216
3297
  }
3217
3298
  ),
3218
3299
  descriptions: Type12.Optional(
@@ -3234,15 +3315,25 @@ var todoSchema = Type12.Object({
3234
3315
  Type12.String({
3235
3316
  description: "Who items are assigned to. Defaults to 'agent'. Applied to all specified items."
3236
3317
  })
3318
+ ),
3319
+ passes: Type12.Optional(
3320
+ Type12.Boolean({
3321
+ description: "Whether the story's quality checks passed. Used in Ralph mode to mark stories as verified. Set to true when checks pass after implementation."
3322
+ })
3323
+ ),
3324
+ learnings: Type12.Optional(
3325
+ Type12.String({
3326
+ description: "Text to append to progress.txt. Required for 'learnings' action. Records patterns, gotchas, and context discovered during implementation."
3327
+ })
3237
3328
  )
3238
3329
  });
3239
- function createTodoTool(threadId) {
3330
+ function createTodoTool(threadId, threadPath) {
3240
3331
  return {
3241
3332
  name: "todo",
3242
3333
  label: "todo",
3243
3334
  description: "Manage todo items to break down the current task into 2 more items. Use 'add' to create items when planning work, 'update' to change an item's status as you progress (pending \u2192 working \u2192 done), 'list' to see all current items, and 'delete' to remove one. Marking an item done does not end the conversation \u2014 continue working on remaining items. Call 'list' at the end so the user sees a completion checklist.",
3244
3335
  parameters: todoSchema,
3245
- execute: async (_toolCallId, { action, descriptions, ids, status, assignedTo }) => {
3336
+ execute: async (_toolCallId, { action, descriptions, ids, status, assignedTo, passes, learnings }) => {
3246
3337
  const db = await getDatabase();
3247
3338
  const now = (/* @__PURE__ */ new Date()).toISOString();
3248
3339
  switch (action) {
@@ -3294,6 +3385,10 @@ function createTodoTool(threadId) {
3294
3385
  updates.push("assignedTo = ?");
3295
3386
  args2.push(assignedTo);
3296
3387
  }
3388
+ if (passes !== void 0) {
3389
+ updates.push("passes = ?");
3390
+ args2.push(passes ? 1 : 0);
3391
+ }
3297
3392
  updates.push("updatedAt = ?");
3298
3393
  args2.push(now);
3299
3394
  args2.push(id, threadId);
@@ -3360,6 +3455,40 @@ function createTodoTool(threadId) {
3360
3455
  details: void 0
3361
3456
  };
3362
3457
  }
3458
+ case "learnings": {
3459
+ if (!learnings?.trim()) {
3460
+ return {
3461
+ content: [
3462
+ {
3463
+ type: "text",
3464
+ text: "Error: learnings text is required for 'learnings' action"
3465
+ }
3466
+ ],
3467
+ details: void 0
3468
+ };
3469
+ }
3470
+ if (!threadPath) {
3471
+ return {
3472
+ content: [
3473
+ {
3474
+ type: "text",
3475
+ text: "Error: threadPath is not available, cannot write progress.txt"
3476
+ }
3477
+ ],
3478
+ details: void 0
3479
+ };
3480
+ }
3481
+ await appendProgress(threadPath, learnings.trim());
3482
+ return {
3483
+ content: [
3484
+ {
3485
+ type: "text",
3486
+ text: "Learnings appended to progress.txt successfully."
3487
+ }
3488
+ ],
3489
+ details: void 0
3490
+ };
3491
+ }
3363
3492
  default:
3364
3493
  return {
3365
3494
  content: [
@@ -3377,16 +3506,16 @@ function createTodoTool(threadId) {
3377
3506
  var todoTool = createTodoTool("");
3378
3507
 
3379
3508
  // src/features/mcp/mcp.config.ts
3380
- import { readFile as readFile3, stat, access } from "fs/promises";
3381
- import { join as join6 } from "path";
3509
+ import { readFile as readFile4, stat, access } from "fs/promises";
3510
+ import { join as join7 } from "path";
3382
3511
  var MCP_CONFIG_PATHS = [".agents/mcp.json", "mcp.json"];
3383
3512
  async function loadMCPConfig(projectPath) {
3384
3513
  for (const configPath of MCP_CONFIG_PATHS) {
3385
- const fullPath = join6(projectPath, configPath);
3514
+ const fullPath = join7(projectPath, configPath);
3386
3515
  try {
3387
3516
  await access(fullPath);
3388
3517
  const fileStats = await stat(fullPath);
3389
- const fileContent = await readFile3(fullPath, "utf-8");
3518
+ const fileContent = await readFile4(fullPath, "utf-8");
3390
3519
  const rawConfig = JSON.parse(fileContent);
3391
3520
  const serversData = rawConfig.servers || rawConfig.mcpServers || {};
3392
3521
  const config = {
@@ -3411,7 +3540,7 @@ async function loadMCPConfig(projectPath) {
3411
3540
  }
3412
3541
  async function getMCPConfigModificationTime(projectPath) {
3413
3542
  for (const configPath of MCP_CONFIG_PATHS) {
3414
- const fullPath = join6(projectPath, configPath);
3543
+ const fullPath = join7(projectPath, configPath);
3415
3544
  try {
3416
3545
  await access(fullPath);
3417
3546
  const fileStats = await stat(fullPath);
@@ -4734,7 +4863,7 @@ async function createCodingTools(cwd, options) {
4734
4863
  createEditTool(cwd),
4735
4864
  createWriteTool(cwd),
4736
4865
  createAskUserTool(),
4737
- createTodoTool(options?.threadId ?? ""),
4866
+ createTodoTool(options?.threadId ?? "", options?.threadPath),
4738
4867
  createFindTool(cwd),
4739
4868
  createCodeSearchTool(cwd, options?.threadId ?? ""),
4740
4869
  createRunWebWorkerTool()
@@ -5274,10 +5403,10 @@ function createAgentTool(options) {
5274
5403
  }
5275
5404
 
5276
5405
  // src/core/paths.ts
5277
- import { join as join7 } from "path";
5406
+ import { join as join8 } from "path";
5278
5407
  import { homedir as homedir3 } from "os";
5279
- var APP_SUPPORT_DIR = join7(homedir3(), "Library", "Application Support", "Tarsk");
5280
- var DATA_DIR = join7(APP_SUPPORT_DIR, "data");
5408
+ var APP_SUPPORT_DIR = join8(homedir3(), "Library", "Application Support", "Tarsk");
5409
+ var DATA_DIR = join8(APP_SUPPORT_DIR, "data");
5281
5410
  function getDataDir() {
5282
5411
  return DATA_DIR;
5283
5412
  }
@@ -5288,7 +5417,7 @@ import { readFileSync as readFileSync4 } from "fs";
5288
5417
 
5289
5418
  // src/project-analyzer.ts
5290
5419
  import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
5291
- import { join as join8 } from "path";
5420
+ import { join as join9 } from "path";
5292
5421
  var ProjectAnalyzer = class {
5293
5422
  projectPath;
5294
5423
  constructor(projectPath = process.cwd()) {
@@ -5299,7 +5428,7 @@ var ProjectAnalyzer = class {
5299
5428
  return this.generateDescription(info);
5300
5429
  }
5301
5430
  getProjectInfo() {
5302
- const packageJsonPath = join8(this.projectPath, "package.json");
5431
+ const packageJsonPath = join9(this.projectPath, "package.json");
5303
5432
  const info = { description: "" };
5304
5433
  if (existsSync7(packageJsonPath)) {
5305
5434
  const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
@@ -5341,15 +5470,15 @@ var ProjectAnalyzer = class {
5341
5470
  }
5342
5471
  }
5343
5472
  detectBuildTool(_scripts = {}, deps, info) {
5344
- if (deps.vite || existsSync7(join8(this.projectPath, "vite.config.js")) || existsSync7(join8(this.projectPath, "vite.config.ts"))) {
5473
+ if (deps.vite || existsSync7(join9(this.projectPath, "vite.config.js")) || existsSync7(join9(this.projectPath, "vite.config.ts"))) {
5345
5474
  info.buildTool = "Vite";
5346
5475
  return;
5347
5476
  }
5348
- if (deps.webpack || existsSync7(join8(this.projectPath, "webpack.config.js"))) {
5477
+ if (deps.webpack || existsSync7(join9(this.projectPath, "webpack.config.js"))) {
5349
5478
  info.buildTool = "Webpack";
5350
5479
  return;
5351
5480
  }
5352
- if (deps.rollup || existsSync7(join8(this.projectPath, "rollup.config.js"))) {
5481
+ if (deps.rollup || existsSync7(join9(this.projectPath, "rollup.config.js"))) {
5353
5482
  info.buildTool = "Rollup";
5354
5483
  return;
5355
5484
  }
@@ -5399,40 +5528,40 @@ var ProjectAnalyzer = class {
5399
5528
  info.uiLibraries = [...new Set(uiLibs)].filter(Boolean);
5400
5529
  }
5401
5530
  detectProjectType(info) {
5402
- if (existsSync7(join8(this.projectPath, ".xcodeproj")) || existsSync7(join8(this.projectPath, ".xcworkspace")) || existsSync7(join8(this.projectPath, "project.pbxproj"))) {
5531
+ if (existsSync7(join9(this.projectPath, ".xcodeproj")) || existsSync7(join9(this.projectPath, ".xcworkspace")) || existsSync7(join9(this.projectPath, "project.pbxproj"))) {
5403
5532
  info.projectType = "Xcode";
5404
- } else if (existsSync7(join8(this.projectPath, "build.gradle")) || existsSync7(join8(this.projectPath, "build.gradle.kts")) || existsSync7(join8(this.projectPath, "app/build.gradle")) || existsSync7(join8(this.projectPath, "settings.gradle"))) {
5533
+ } else if (existsSync7(join9(this.projectPath, "build.gradle")) || existsSync7(join9(this.projectPath, "build.gradle.kts")) || existsSync7(join9(this.projectPath, "app/build.gradle")) || existsSync7(join9(this.projectPath, "settings.gradle"))) {
5405
5534
  info.projectType = "Android Studio";
5406
- } else if (existsSync7(join8(this.projectPath, "pubspec.yaml"))) {
5535
+ } else if (existsSync7(join9(this.projectPath, "pubspec.yaml"))) {
5407
5536
  info.projectType = "Flutter";
5408
- } else if (existsSync7(join8(this.projectPath, "go.mod"))) {
5537
+ } else if (existsSync7(join9(this.projectPath, "go.mod"))) {
5409
5538
  info.projectType = "Go";
5410
- } else if (existsSync7(join8(this.projectPath, "Cargo.toml"))) {
5539
+ } else if (existsSync7(join9(this.projectPath, "Cargo.toml"))) {
5411
5540
  info.projectType = "Rust";
5412
- } else if (existsSync7(join8(this.projectPath, "requirements.txt")) || existsSync7(join8(this.projectPath, "pyproject.toml")) || existsSync7(join8(this.projectPath, "setup.py"))) {
5541
+ } else if (existsSync7(join9(this.projectPath, "requirements.txt")) || existsSync7(join9(this.projectPath, "pyproject.toml")) || existsSync7(join9(this.projectPath, "setup.py"))) {
5413
5542
  info.projectType = "Python";
5414
- } else if (existsSync7(join8(this.projectPath, "Gemfile"))) {
5543
+ } else if (existsSync7(join9(this.projectPath, "Gemfile"))) {
5415
5544
  info.projectType = "Ruby";
5416
- } else if (existsSync7(join8(this.projectPath, "composer.json"))) {
5545
+ } else if (existsSync7(join9(this.projectPath, "composer.json"))) {
5417
5546
  info.projectType = "PHP";
5418
- } else if (existsSync7(join8(this.projectPath, "pom.xml")) || existsSync7(join8(this.projectPath, "build.xml"))) {
5547
+ } else if (existsSync7(join9(this.projectPath, "pom.xml")) || existsSync7(join9(this.projectPath, "build.xml"))) {
5419
5548
  info.projectType = "Java";
5420
- } else if (existsSync7(join8(this.projectPath, ".csproj")) || existsSync7(join8(this.projectPath, "project.json"))) {
5549
+ } else if (existsSync7(join9(this.projectPath, ".csproj")) || existsSync7(join9(this.projectPath, "project.json"))) {
5421
5550
  info.projectType = ".NET";
5422
5551
  }
5423
5552
  }
5424
5553
  detectPackageManager(info) {
5425
- if (existsSync7(join8(this.projectPath, "bun.lockb"))) {
5554
+ if (existsSync7(join9(this.projectPath, "bun.lockb"))) {
5426
5555
  info.packageManager = "Bun";
5427
- } else if (existsSync7(join8(this.projectPath, "bun.lock"))) {
5556
+ } else if (existsSync7(join9(this.projectPath, "bun.lock"))) {
5428
5557
  info.packageManager = "Bun";
5429
- } else if (existsSync7(join8(this.projectPath, "pnpm-lock.yaml"))) {
5558
+ } else if (existsSync7(join9(this.projectPath, "pnpm-lock.yaml"))) {
5430
5559
  info.packageManager = "pnpm";
5431
- } else if (existsSync7(join8(this.projectPath, "yarn.lock"))) {
5560
+ } else if (existsSync7(join9(this.projectPath, "yarn.lock"))) {
5432
5561
  info.packageManager = "Yarn";
5433
- } else if (existsSync7(join8(this.projectPath, "package-lock.json"))) {
5562
+ } else if (existsSync7(join9(this.projectPath, "package-lock.json"))) {
5434
5563
  info.packageManager = "npm";
5435
- } else if (existsSync7(join8(this.projectPath, "npm-shrinkwrap.json"))) {
5564
+ } else if (existsSync7(join9(this.projectPath, "npm-shrinkwrap.json"))) {
5436
5565
  info.packageManager = "npm";
5437
5566
  }
5438
5567
  }
@@ -5494,8 +5623,8 @@ function analyzeProject(projectPath) {
5494
5623
  }
5495
5624
 
5496
5625
  // src/features/rules/rules.manager.ts
5497
- import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
5498
- import { join as join9, relative as relative4 } from "path";
5626
+ import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
5627
+ import { join as join10, relative as relative4 } from "path";
5499
5628
  import { existsSync as existsSync8 } from "fs";
5500
5629
  import { homedir as homedir4 } from "os";
5501
5630
  function parseRuleFrontmatter(markdown) {
@@ -5539,10 +5668,10 @@ function parseRuleFrontmatter(markdown) {
5539
5668
  return { metadata, content };
5540
5669
  }
5541
5670
  function getGlobalRulesDir() {
5542
- return join9(homedir4(), ".agents", "rules");
5671
+ return join10(homedir4(), ".agents", "rules");
5543
5672
  }
5544
5673
  function getProjectRulesDir(threadPath) {
5545
- return join9(threadPath, ".agents", "rules");
5674
+ return join10(threadPath, ".agents", "rules");
5546
5675
  }
5547
5676
  var RuleManager = class {
5548
5677
  /**
@@ -5575,13 +5704,13 @@ var RuleManager = class {
5575
5704
  try {
5576
5705
  const entries = await readdir3(dir, { withFileTypes: true });
5577
5706
  for (const entry of entries) {
5578
- const fullPath = join9(dir, entry.name);
5707
+ const fullPath = join10(dir, entry.name);
5579
5708
  if (entry.isDirectory()) {
5580
5709
  const subRules = await this.loadRulesFromDir(fullPath, threadPath, scope);
5581
5710
  rules.push(...subRules);
5582
5711
  } else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdc"))) {
5583
5712
  try {
5584
- const fileContent = await readFile4(fullPath, "utf-8");
5713
+ const fileContent = await readFile5(fullPath, "utf-8");
5585
5714
  const { metadata, content } = parseRuleFrontmatter(fileContent);
5586
5715
  const ruleName = entry.name.replace(/\.(md|mdc)$/, "");
5587
5716
  const relativePath = relative4(threadPath, fullPath);
@@ -5741,6 +5870,43 @@ Structure your response as:
5741
5870
  YOU MUST use the ask_user tool to ask clarifying questions if needed and to confirm implementation of the plan.
5742
5871
 
5743
5872
  Remember: You are ONLY planning. Do NOT execute any changes.`;
5873
+ var RALPH_MODE_INSTRUCTIONS = `
5874
+
5875
+ # Ralph Mode (ACTIVE)
5876
+
5877
+ You are currently in RALPH MODE. This is an autonomous iterative loop that implements one story per iteration with fresh context between iterations.
5878
+
5879
+ ## How Ralph Mode Works
5880
+ - You receive a list of stories (managed via the todo tool) and implement ONE story per iteration.
5881
+ - After each iteration, your context is cleared. Memory persists via git history, progress.txt, and the todo list state.
5882
+ - The loop continues automatically until all stories pass or the iteration limit is reached.
5883
+
5884
+ ## What you MUST do each iteration:
5885
+ 1. Call the todo tool with action 'list' to see all current stories and their status.
5886
+ 2. Pick the highest-priority story where passes is false (earliest in the list).
5887
+ 3. Implement ONLY that one story. Do not work on multiple stories.
5888
+ 4. After implementation, run the project's quality checks (typecheck, tests, lint). Use the project's validation script or the check commands from the developer context (e.g. bun run check:all).
5889
+ 5. If checks pass:
5890
+ - Mark the story as done with passes=true using the todo tool: action='update', ids=[storyId], status='done', passes=true
5891
+ - Commit your changes with a clear commit message describing the story.
5892
+ - Use the todo tool with action='learnings' to record what you learned (patterns discovered, gotchas, useful context).
5893
+ 6. If checks fail: fix the issues and re-run checks. Keep iterating until they pass.
5894
+ 7. After completing the story and recording learnings, output the marker: <ralph-iteration-complete/>
5895
+
5896
+ ## What you MUST NOT do:
5897
+ - Do NOT work on more than one story per iteration.
5898
+ - Do NOT skip running quality checks.
5899
+ - Do NOT use emojis.
5900
+
5901
+ ## Story sizing:
5902
+ Each story should be small enough to implement within one context window. If a story is too large, break it down before implementing.
5903
+
5904
+ ## Progress and learnings:
5905
+ Use the todo tool's 'learnings' action to record:
5906
+ - Patterns discovered (e.g. "this codebase uses X for Y")
5907
+ - Gotchas (e.g. "do not forget to update Z when changing W")
5908
+ - Useful context for future iterations
5909
+ `;
5744
5910
  function loadDeveloperContext(threadPath) {
5745
5911
  try {
5746
5912
  const agentsPath = resolve(threadPath, "agents.md");
@@ -5879,7 +6045,7 @@ function formatAgentsSection(agents) {
5879
6045
  );
5880
6046
  return section;
5881
6047
  }
5882
- async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools) {
6048
+ async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools, ralphMode) {
5883
6049
  let prompt = buildDefaultPrompt(tools);
5884
6050
  const agentsContent = loadDeveloperContext(threadPath);
5885
6051
  if (agentsContent) {
@@ -5900,6 +6066,13 @@ async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents
5900
6066
  if (planMode) {
5901
6067
  prompt += PLAN_MODE_INSTRUCTIONS;
5902
6068
  }
6069
+ if (ralphMode) {
6070
+ prompt += RALPH_MODE_INSTRUCTIONS;
6071
+ const progressContent = await readProgress(threadPath);
6072
+ if (progressContent.trim()) {
6073
+ prompt += "\n\n# Prior Learnings (from progress.txt)\n\n" + progressContent;
6074
+ }
6075
+ }
5903
6076
  const skillsSection = formatSkillsSection(skills ?? []);
5904
6077
  if (skillsSection) {
5905
6078
  prompt += skillsSection;
@@ -6008,6 +6181,7 @@ var PiExecutorImpl = class {
6008
6181
  const { tools, deferredToolNames } = await createCodingTools(cwd, {
6009
6182
  skills: context.skills,
6010
6183
  threadId: context.threadId,
6184
+ threadPath: cwd,
6011
6185
  metadataManager: this.metadataManager
6012
6186
  });
6013
6187
  const systemPrompt = await loadAgentSystemPrompt(
@@ -6016,7 +6190,8 @@ var PiExecutorImpl = class {
6016
6190
  context.skills,
6017
6191
  context.planMode,
6018
6192
  context.agents,
6019
- deferredToolNames
6193
+ deferredToolNames,
6194
+ context.ralphMode
6020
6195
  );
6021
6196
  console.log(
6022
6197
  "[ai] System prompt loaded from thread path",
@@ -6458,8 +6633,8 @@ import { Hono as Hono2 } from "hono";
6458
6633
  import { randomUUID as randomUUID4 } from "crypto";
6459
6634
 
6460
6635
  // src/features/skills/skills.manager.ts
6461
- import { readdir as readdir4, readFile as readFile5 } from "fs/promises";
6462
- import { join as join10 } from "path";
6636
+ import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
6637
+ import { join as join11 } from "path";
6463
6638
  import { existsSync as existsSync9 } from "fs";
6464
6639
  import { homedir as homedir5 } from "os";
6465
6640
  function parseFrontmatter(markdown) {
@@ -6507,10 +6682,10 @@ function parseFrontmatter(markdown) {
6507
6682
  return { metadata, content };
6508
6683
  }
6509
6684
  function getGlobalSkillsDir() {
6510
- return join10(homedir5(), ".agents", "skills");
6685
+ return join11(homedir5(), ".agents", "skills");
6511
6686
  }
6512
6687
  function getProjectSkillsDir(threadPath) {
6513
- return join10(threadPath, ".agents", "skills");
6688
+ return join11(threadPath, ".agents", "skills");
6514
6689
  }
6515
6690
  function validateSkillName(name) {
6516
6691
  if (!name || name.length === 0 || name.length > 64) {
@@ -6563,14 +6738,14 @@ var SkillManager = class {
6563
6738
  for (const entry of entries) {
6564
6739
  if (!entry.isDirectory()) continue;
6565
6740
  const skillDirName = entry.name;
6566
- const skillPath = join10(dir, skillDirName);
6567
- const skillFilePath = join10(skillPath, "SKILL.md");
6741
+ const skillPath = join11(dir, skillDirName);
6742
+ const skillFilePath = join11(skillPath, "SKILL.md");
6568
6743
  if (!existsSync9(skillFilePath)) {
6569
6744
  console.warn(`Skipping skill directory ${skillDirName}: SKILL.md not found`);
6570
6745
  continue;
6571
6746
  }
6572
6747
  try {
6573
- const fileContent = await readFile5(skillFilePath, "utf-8");
6748
+ const fileContent = await readFile6(skillFilePath, "utf-8");
6574
6749
  const { metadata, content } = parseFrontmatter(fileContent);
6575
6750
  if (!metadata.name) {
6576
6751
  console.warn(`Skipping skill in ${skillDirName}: missing 'name' in frontmatter`);
@@ -6743,8 +6918,8 @@ async function activateSkills(allSkills, taskDescription, thread, options) {
6743
6918
  }
6744
6919
 
6745
6920
  // src/features/agents/agents.manager.ts
6746
- import { readdir as readdir5, readFile as readFile6 } from "fs/promises";
6747
- import { join as join11 } from "path";
6921
+ import { readdir as readdir5, readFile as readFile7 } from "fs/promises";
6922
+ import { join as join12 } from "path";
6748
6923
  import { existsSync as existsSync10 } from "fs";
6749
6924
  import { homedir as homedir6 } from "os";
6750
6925
  function parseFrontmatter2(markdown) {
@@ -6802,10 +6977,10 @@ function validateDescription2(description) {
6802
6977
  return !!description && description.length > 0 && description.length <= 1024;
6803
6978
  }
6804
6979
  function getGlobalAgentsDir() {
6805
- return join11(homedir6(), ".agents", "agents");
6980
+ return join12(homedir6(), ".agents", "agents");
6806
6981
  }
6807
6982
  function getProjectAgentsDir(threadPath) {
6808
- return join11(threadPath, ".agents", "agents");
6983
+ return join12(threadPath, ".agents", "agents");
6809
6984
  }
6810
6985
  var AgentsManager = class {
6811
6986
  /**
@@ -6837,14 +7012,14 @@ var AgentsManager = class {
6837
7012
  for (const entry of entries) {
6838
7013
  if (!entry.isDirectory()) continue;
6839
7014
  const agentDirName = entry.name;
6840
- const agentPath = join11(dir, agentDirName);
6841
- const agentFilePath = join11(agentPath, "AGENT.md");
7015
+ const agentPath = join12(dir, agentDirName);
7016
+ const agentFilePath = join12(agentPath, "AGENT.md");
6842
7017
  if (!existsSync10(agentFilePath)) {
6843
7018
  console.warn(`[agents] Skipping agent directory ${agentDirName}: AGENT.md not found`);
6844
7019
  continue;
6845
7020
  }
6846
7021
  try {
6847
- const fileContent = await readFile6(agentFilePath, "utf-8");
7022
+ const fileContent = await readFile7(agentFilePath, "utf-8");
6848
7023
  const { metadata, content } = parseFrontmatter2(fileContent);
6849
7024
  if (!metadata.name) {
6850
7025
  console.warn(
@@ -7024,8 +7199,8 @@ function extractAssistantContent(events, fallback) {
7024
7199
 
7025
7200
  // src/features/chat/chat-post.route.ts
7026
7201
  init_database();
7027
- import { readFile as readFile7, unlink } from "fs/promises";
7028
- import { join as join12 } from "path";
7202
+ import { readFile as readFile8, unlink } from "fs/promises";
7203
+ import { join as join13 } from "path";
7029
7204
 
7030
7205
  // src/features/project-todos/project-todos.database.ts
7031
7206
  init_database();
@@ -7592,7 +7767,15 @@ function runValidationScript(script, cwd) {
7592
7767
  async function postChatMessage(c, threadManager, agentExecutor, conversationManager, processingStateManager) {
7593
7768
  try {
7594
7769
  const body = await c.req.json();
7595
- const { threadId, content, model: baseModel, provider, attachments, planMode } = body;
7770
+ const {
7771
+ threadId,
7772
+ content,
7773
+ model: baseModel,
7774
+ provider,
7775
+ attachments,
7776
+ planMode,
7777
+ ralphMode
7778
+ } = body;
7596
7779
  let model = baseModel;
7597
7780
  if (provider && typeof provider === "string") {
7598
7781
  const providerPrefix = provider.toLowerCase();
@@ -7716,6 +7899,7 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
7716
7899
  provider,
7717
7900
  attachments,
7718
7901
  planMode,
7902
+ ralphMode,
7719
7903
  skills: activatedSkills,
7720
7904
  agents
7721
7905
  };
@@ -7741,7 +7925,9 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
7741
7925
  content,
7742
7926
  model,
7743
7927
  attachments,
7744
- planMode
7928
+ planMode,
7929
+ void 0,
7930
+ ralphMode
7745
7931
  );
7746
7932
  const history = await conversationManager.getConversationHistoryByConversationId(
7747
7933
  threadId,
@@ -7854,7 +8040,9 @@ ${result.output}`;
7854
8040
  validationPrompt,
7855
8041
  model,
7856
8042
  void 0,
7857
- planMode
8043
+ planMode,
8044
+ void 0,
8045
+ ralphMode
7858
8046
  );
7859
8047
  const validationCaptured = { events: [], fullContent: "" };
7860
8048
  yield* executeAgent(
@@ -7890,6 +8078,82 @@ ${result.output}`;
7890
8078
  yield maxAttemptsEvent;
7891
8079
  }
7892
8080
  }
8081
+ if (ralphMode) {
8082
+ const MAX_RALPH_ITERATIONS = 10;
8083
+ let ralphIteration = 1;
8084
+ while (ralphIteration < MAX_RALPH_ITERATIONS) {
8085
+ if (abortController.signal.aborted) break;
8086
+ const db2 = await getDatabase();
8087
+ const todos = await fetchAllTodos(db2, threadId);
8088
+ const remaining = todos.filter((t) => !t.passes);
8089
+ if (remaining.length === 0) {
8090
+ const completeEvent = {
8091
+ type: "thinking",
8092
+ content: `[Ralph] All ${todos.length} stories completed successfully.`
8093
+ };
8094
+ processingStateManager.pushEvent(threadId, completeEvent);
8095
+ yield completeEvent;
8096
+ break;
8097
+ }
8098
+ ralphIteration++;
8099
+ const iterationEvent = {
8100
+ type: "thinking",
8101
+ content: `[Ralph] Iteration ${ralphIteration}/${MAX_RALPH_ITERATIONS} - ${remaining.length} stories remaining. Clearing context and starting next story...`
8102
+ };
8103
+ processingStateManager.pushEvent(threadId, iterationEvent);
8104
+ yield iterationEvent;
8105
+ const newConversationId = randomUUID4();
8106
+ await threadManager.updateThread(threadId, {
8107
+ currentConversationId: newConversationId
8108
+ });
8109
+ const ralphPrompt = "Continue the Ralph loop. List all stories, pick the next incomplete one (passes=false), and implement it. Follow the Ralph mode instructions.";
8110
+ const iterMessageId = await conversationManager.startMessage(
8111
+ threadId,
8112
+ threadPath,
8113
+ newConversationId,
8114
+ ralphPrompt,
8115
+ model,
8116
+ void 0,
8117
+ void 0,
8118
+ void 0,
8119
+ true
8120
+ );
8121
+ const iterCaptured = { events: [], fullContent: "" };
8122
+ const iterContext = { ...context, attachments: void 0 };
8123
+ yield* executeAgent(
8124
+ ralphPrompt,
8125
+ iterContext,
8126
+ abortController.signal,
8127
+ [],
8128
+ // Empty history - fresh context
8129
+ iterCaptured
8130
+ );
8131
+ const iterContent = extractAssistantContent(
8132
+ iterCaptured.events,
8133
+ iterCaptured.fullContent
8134
+ );
8135
+ await conversationManager.completeMessage(
8136
+ iterMessageId,
8137
+ iterContent,
8138
+ iterCaptured.events
8139
+ );
8140
+ decrementBalance().catch((err) => {
8141
+ console.error("[ChatRoute] Failed to decrement balance:", err);
8142
+ });
8143
+ }
8144
+ if (ralphIteration >= MAX_RALPH_ITERATIONS) {
8145
+ const finalTodos = await fetchAllTodos(db, threadId);
8146
+ const remaining = finalTodos.filter((todo) => todo.status !== "done");
8147
+ if (remaining.length > 0) {
8148
+ const limitEvent = {
8149
+ type: "thinking",
8150
+ content: `[Ralph] Reached maximum iteration limit (${MAX_RALPH_ITERATIONS}). Stopping.`
8151
+ };
8152
+ processingStateManager.pushEvent(threadId, limitEvent);
8153
+ yield limitEvent;
8154
+ }
8155
+ }
8156
+ }
7893
8157
  } catch (error) {
7894
8158
  const errorMessage = error instanceof Error ? error.message : String(error);
7895
8159
  const errorEvent = {
@@ -7923,12 +8187,15 @@ ${result.output}`;
7923
8187
  const db = await getDatabase();
7924
8188
  await invalidateGitStatusCache(db, threadId);
7925
8189
  await clearWorkingByThreadId(db, threadId);
8190
+ if (!ralphMode) {
8191
+ await clearTodosIfAllDone(db, threadId);
8192
+ }
7926
8193
  try {
7927
8194
  const todo = await getTodoByThreadId(db, threadId);
7928
8195
  if (todo && todo.status === "Plan") {
7929
- const planFilePath = join12(threadPath, `${todo.id}-plan.md`);
8196
+ const planFilePath = join13(threadPath, `${todo.id}-plan.md`);
7930
8197
  try {
7931
- const planContent = await readFile7(planFilePath, "utf-8");
8198
+ const planContent = await readFile8(planFilePath, "utf-8");
7932
8199
  await updateTodo(db, todo.id, { description: planContent.trim() });
7933
8200
  await unlink(planFilePath);
7934
8201
  } catch {
@@ -8094,7 +8361,7 @@ init_database();
8094
8361
 
8095
8362
  // src/features/conversations/conversations.database.ts
8096
8363
  import { randomUUID as randomUUID6 } from "crypto";
8097
- async function insertMessage(db, threadId, conversationId, message, model, attachments, planMode, imageMode) {
8364
+ async function insertMessage(db, threadId, conversationId, message, model, attachments, planMode, imageMode, ralphMode) {
8098
8365
  try {
8099
8366
  const messageId = randomUUID6();
8100
8367
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -8102,9 +8369,9 @@ async function insertMessage(db, threadId, conversationId, message, model, attac
8102
8369
  `
8103
8370
  INSERT INTO conversation_history (
8104
8371
  id, threadId, conversationId, timestamp, status,
8105
- request_message, request_model, request_attachments, request_planMode, request_imageMode
8372
+ request_message, request_model, request_attachments, request_planMode, request_ralphMode, request_imageMode
8106
8373
  )
8107
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8374
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8108
8375
  `,
8109
8376
  [
8110
8377
  messageId,
@@ -8116,6 +8383,7 @@ async function insertMessage(db, threadId, conversationId, message, model, attac
8116
8383
  model,
8117
8384
  attachments ? JSON.stringify(attachments) : null,
8118
8385
  planMode ? 1 : 0,
8386
+ ralphMode ? 1 : 0,
8119
8387
  imageMode ? 1 : 0
8120
8388
  ]
8121
8389
  );
@@ -8289,6 +8557,7 @@ function deserializeMessage(row) {
8289
8557
  model: row.request_model,
8290
8558
  ...row.request_attachments && { attachments: JSON.parse(row.request_attachments) },
8291
8559
  ...row.request_planMode === 1 && { planMode: true },
8560
+ ...row.request_ralphMode === 1 && { ralphMode: true },
8292
8561
  ...row.request_imageMode === 1 && { imageMode: true }
8293
8562
  },
8294
8563
  response: row.response_content != null ? {
@@ -8368,7 +8637,7 @@ var ConversationManagerImpl = class {
8368
8637
  /**
8369
8638
  * Starts a new conversation message
8370
8639
  */
8371
- async startMessage(threadId, _threadPath, conversationId, message, model, attachments, planMode, imageMode) {
8640
+ async startMessage(threadId, _threadPath, conversationId, message, model, attachments, planMode, imageMode, ralphMode) {
8372
8641
  try {
8373
8642
  if (!this.db) {
8374
8643
  await this.initialize();
@@ -8381,7 +8650,8 @@ var ConversationManagerImpl = class {
8381
8650
  model,
8382
8651
  attachments,
8383
8652
  planMode,
8384
- imageMode
8653
+ imageMode,
8654
+ ralphMode
8385
8655
  );
8386
8656
  } catch (error) {
8387
8657
  console.error("Failed to start conversation message:", error);
@@ -10662,12 +10932,12 @@ async function handleCheckRunning(c, projectManager) {
10662
10932
  }
10663
10933
 
10664
10934
  // src/core/run-command-detector.ts
10665
- import { readFile as readFile8 } from "fs/promises";
10666
- import { join as join13 } from "path";
10935
+ import { readFile as readFile9 } from "fs/promises";
10936
+ import { join as join14 } from "path";
10667
10937
  async function detectPackageManager(projectPath) {
10668
10938
  try {
10669
- const packageJsonPath = join13(projectPath, "package.json");
10670
- const content = await readFile8(packageJsonPath, "utf-8");
10939
+ const packageJsonPath = join14(projectPath, "package.json");
10940
+ const content = await readFile9(packageJsonPath, "utf-8");
10671
10941
  const packageJson = JSON.parse(content);
10672
10942
  if (packageJson.packageManager) {
10673
10943
  const pm = packageJson.packageManager.split("@")[0];
@@ -10682,8 +10952,8 @@ async function detectPackageManager(projectPath) {
10682
10952
  }
10683
10953
  async function detectRunScripts(projectPath) {
10684
10954
  try {
10685
- const packageJsonPath = join13(projectPath, "package.json");
10686
- const content = await readFile8(packageJsonPath, "utf-8");
10955
+ const packageJsonPath = join14(projectPath, "package.json");
10956
+ const content = await readFile9(packageJsonPath, "utf-8");
10687
10957
  const packageJson = JSON.parse(content);
10688
10958
  const scripts = packageJson.scripts ?? {};
10689
10959
  return {
@@ -10728,8 +10998,8 @@ async function suggestRunCommand(projectPath) {
10728
10998
  }
10729
10999
 
10730
11000
  // src/features/projects/projects-package-scripts.route.ts
10731
- import { readFile as readFile9 } from "fs/promises";
10732
- import { join as join14 } from "path";
11001
+ import { readFile as readFile10 } from "fs/promises";
11002
+ import { join as join15 } from "path";
10733
11003
  import { glob } from "glob";
10734
11004
  function formatScriptName(scriptName) {
10735
11005
  return scriptName.replace(/[-:_]/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
@@ -10757,8 +11027,8 @@ async function findPackageScripts(projectPath) {
10757
11027
  const packageManager = await detectPackageManager(projectPath);
10758
11028
  for (const filePath of packageJsonFiles) {
10759
11029
  try {
10760
- const fullPath = join14(projectPath, filePath);
10761
- const content = await readFile9(fullPath, "utf-8");
11030
+ const fullPath = join15(projectPath, filePath);
11031
+ const content = await readFile10(fullPath, "utf-8");
10762
11032
  const packageJson = JSON.parse(content);
10763
11033
  if (packageJson.scripts) {
10764
11034
  for (const [scriptName] of Object.entries(packageJson.scripts)) {
@@ -10805,8 +11075,8 @@ async function handleGetPackageScripts(c, projectManager) {
10805
11075
  }
10806
11076
 
10807
11077
  // src/features/projects/projects-ai-files.route.ts
10808
- import { readdir as readdir6, readFile as readFile10, writeFile, mkdir, access as access2, rm } from "fs/promises";
10809
- import { join as join15, extname as extname3, basename } from "path";
11078
+ import { readdir as readdir6, readFile as readFile11, writeFile as writeFile2, mkdir, access as access2, rm } from "fs/promises";
11079
+ import { join as join16, extname as extname3, basename } from "path";
10810
11080
  import { existsSync as existsSync11 } from "fs";
10811
11081
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
10812
11082
  ".git",
@@ -10825,8 +11095,8 @@ async function buildFullTree(dirPath, relativeDirPath) {
10825
11095
  try {
10826
11096
  const entries = await readdir6(dirPath, { withFileTypes: true });
10827
11097
  for (const entry of entries) {
10828
- const entryRelPath = join15(relativeDirPath, entry.name);
10829
- const entryAbsPath = join15(dirPath, entry.name);
11098
+ const entryRelPath = join16(relativeDirPath, entry.name);
11099
+ const entryAbsPath = join16(dirPath, entry.name);
10830
11100
  if (entry.isDirectory()) {
10831
11101
  const children = await buildFullTree(entryAbsPath, entryRelPath);
10832
11102
  nodes.push({
@@ -10854,8 +11124,8 @@ async function buildMarkdownFilteredTree(dirPath, relativeDirPath) {
10854
11124
  try {
10855
11125
  const entries = await readdir6(dirPath, { withFileTypes: true });
10856
11126
  for (const entry of entries) {
10857
- const entryRelPath = join15(relativeDirPath, entry.name);
10858
- const entryAbsPath = join15(dirPath, entry.name);
11127
+ const entryRelPath = join16(relativeDirPath, entry.name);
11128
+ const entryAbsPath = join16(dirPath, entry.name);
10859
11129
  if (entry.isDirectory()) {
10860
11130
  if (IGNORED_DIRS.has(entry.name)) continue;
10861
11131
  const children = await buildMarkdownFilteredTree(entryAbsPath, entryRelPath);
@@ -10890,8 +11160,8 @@ async function buildAIFileTree(projectPath) {
10890
11160
  { key: "agents", label: "Agents" }
10891
11161
  ];
10892
11162
  for (const { key, label } of agentsFolders) {
10893
- const relPath = join15(".agents", key);
10894
- const absPath = join15(projectPath, relPath);
11163
+ const relPath = join16(".agents", key);
11164
+ const absPath = join16(projectPath, relPath);
10895
11165
  const exists = existsSync11(absPath);
10896
11166
  if (exists) {
10897
11167
  const children = await buildFullTree(absPath, relPath);
@@ -10926,7 +11196,7 @@ async function buildAIFileTree(projectPath) {
10926
11196
  if (entry.name === ".agents" || entry.name === "AGENTS.md" || IGNORED_DIRS.has(entry.name))
10927
11197
  continue;
10928
11198
  const entryRelPath = entry.name;
10929
- const entryAbsPath = join15(projectPath, entry.name);
11199
+ const entryAbsPath = join16(projectPath, entry.name);
10930
11200
  if (entry.isFile() && MARKDOWN_EXTS.has(extname3(entry.name))) {
10931
11201
  nodes.push({
10932
11202
  id: entryRelPath,
@@ -10953,7 +11223,7 @@ async function buildAIFileTree(projectPath) {
10953
11223
  }
10954
11224
  function validateFilePath(projectPath, filePath) {
10955
11225
  if (!filePath) return null;
10956
- const abs = join15(projectPath, filePath);
11226
+ const abs = join16(projectPath, filePath);
10957
11227
  if (!abs.startsWith(projectPath + "/") && abs !== projectPath) return null;
10958
11228
  return abs;
10959
11229
  }
@@ -11040,7 +11310,7 @@ Links to important documentation, tools, or references.
11040
11310
  } catch {
11041
11311
  return c.json({ error: { code: "NOT_FOUND", message: `File not found: ${filePath}` } }, 404);
11042
11312
  }
11043
- const content = await readFile10(absPath, "utf-8");
11313
+ const content = await readFile11(absPath, "utf-8");
11044
11314
  return c.json({ content, path: filePath });
11045
11315
  } catch (error) {
11046
11316
  return errorResponse(
@@ -11079,9 +11349,9 @@ async function handleSaveAIFile(c, projectManager) {
11079
11349
  if (!absPath) {
11080
11350
  return c.json({ error: { code: "BAD_REQUEST", message: "Invalid file path" } }, 400);
11081
11351
  }
11082
- const parentDir = join15(absPath, "..");
11352
+ const parentDir = join16(absPath, "..");
11083
11353
  await mkdir(parentDir, { recursive: true });
11084
- await writeFile(absPath, content, "utf-8");
11354
+ await writeFile2(absPath, content, "utf-8");
11085
11355
  return c.json({ success: true, path: filePath });
11086
11356
  } catch (error) {
11087
11357
  return errorResponse(
@@ -11179,10 +11449,10 @@ async function handleCreateSkill(c, projectManager) {
11179
11449
  if (!project) {
11180
11450
  return errorResponse(c, ErrorCodes.PROJECT_NOT_FOUND, `Project not found: ${projectId}`, 404);
11181
11451
  }
11182
- const skillRelPath = join15(".agents", "skills", name);
11183
- const skillAbsPath = join15(project.path, skillRelPath);
11184
- const skillFileRelPath = join15(skillRelPath, "SKILL.md");
11185
- const skillFileAbsPath = join15(skillAbsPath, "SKILL.md");
11452
+ const skillRelPath = join16(".agents", "skills", name);
11453
+ const skillAbsPath = join16(project.path, skillRelPath);
11454
+ const skillFileRelPath = join16(skillRelPath, "SKILL.md");
11455
+ const skillFileAbsPath = join16(skillAbsPath, "SKILL.md");
11186
11456
  if (existsSync11(skillAbsPath)) {
11187
11457
  return c.json(
11188
11458
  { error: { code: "CONFLICT", message: `Skill '${name}' already exists` } },
@@ -11197,7 +11467,7 @@ description: ${description}
11197
11467
 
11198
11468
  Place your skill instructions here as markdown. This will be used when Tarsk is prompted with a request related to the description of this skill.
11199
11469
  `;
11200
- await writeFile(skillFileAbsPath, skillContent, "utf-8");
11470
+ await writeFile2(skillFileAbsPath, skillContent, "utf-8");
11201
11471
  return c.json({
11202
11472
  success: true,
11203
11473
  path: skillFileRelPath,
@@ -11284,7 +11554,7 @@ function createProjectRoutes(projectManager, threadManager) {
11284
11554
 
11285
11555
  // src/features/projects/projects.manager.ts
11286
11556
  init_utils();
11287
- import { join as join18 } from "path";
11557
+ import { join as join19 } from "path";
11288
11558
  import { realpathSync as realpathSync2 } from "fs";
11289
11559
  import { rm as rm3 } from "fs/promises";
11290
11560
 
@@ -11505,14 +11775,14 @@ var ProcessManager = class extends EventEmitter {
11505
11775
  // src/features/projects/projects.creator.ts
11506
11776
  init_utils();
11507
11777
  import { randomUUID as randomUUID7 } from "crypto";
11508
- import { basename as basename2, join as join17, relative as relative5 } from "path";
11778
+ import { basename as basename2, join as join18, relative as relative5 } from "path";
11509
11779
  import { access as access3, stat as stat3, readdir as readdir8 } from "fs/promises";
11510
11780
 
11511
11781
  // src/features/scaffold/scaffold.runner.ts
11512
11782
  init_utils();
11513
11783
  import { existsSync as existsSync12 } from "fs";
11514
- import { join as join16 } from "path";
11515
- import { mkdir as mkdir2, readdir as readdir7, stat as stat2, rename, rm as rm2, writeFile as writeFile2 } from "fs/promises";
11784
+ import { join as join17 } from "path";
11785
+ import { mkdir as mkdir2, readdir as readdir7, stat as stat2, rename, rm as rm2, writeFile as writeFile3 } from "fs/promises";
11516
11786
  import { createInterface as createInterface2 } from "readline";
11517
11787
 
11518
11788
  // src/scaffold-templates.json
@@ -12233,9 +12503,9 @@ function loadCatalog() {
12233
12503
  }
12234
12504
  async function writeAgentsMd(projectPath, agentsMd) {
12235
12505
  if (!agentsMd) return;
12236
- const agentsPath = join16(projectPath, "AGENTS.md");
12506
+ const agentsPath = join17(projectPath, "AGENTS.md");
12237
12507
  if (existsSync12(agentsPath)) return;
12238
- await writeFile2(agentsPath, agentsMd, "utf-8");
12508
+ await writeFile3(agentsPath, agentsMd, "utf-8");
12239
12509
  }
12240
12510
  function getProjectName(name) {
12241
12511
  return name.toLowerCase().replace(/\s/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-(\d)/g, "-a$1");
@@ -12391,7 +12661,7 @@ async function* runCommandStreaming(cwd, command) {
12391
12661
  }
12392
12662
  }
12393
12663
  async function* createScaffoldedProjectStreaming(options) {
12394
- const projectPath = join16(options.parentDir, options.threadId);
12664
+ const projectPath = join17(options.parentDir, options.threadId);
12395
12665
  if (!existsSync12(options.parentDir)) {
12396
12666
  yield {
12397
12667
  type: "result",
@@ -12481,7 +12751,7 @@ async function* createScaffoldedProjectStreaming(options) {
12481
12751
  }
12482
12752
  try {
12483
12753
  const projectName2 = getProjectName(options.projectName);
12484
- const projectSubDir = join16(projectPath, projectName2);
12754
+ const projectSubDir = join17(projectPath, projectName2);
12485
12755
  try {
12486
12756
  const subDirStat = await stat2(projectSubDir);
12487
12757
  if (subDirStat.isDirectory()) {
@@ -12491,8 +12761,8 @@ async function* createScaffoldedProjectStreaming(options) {
12491
12761
  };
12492
12762
  const items = await readdir7(projectSubDir);
12493
12763
  for (const item of items) {
12494
- const oldPath = join16(projectSubDir, item);
12495
- const newPath = join16(projectPath, item);
12764
+ const oldPath = join17(projectSubDir, item);
12765
+ const newPath = join17(projectPath, item);
12496
12766
  await rename(oldPath, newPath);
12497
12767
  }
12498
12768
  await rm2(projectSubDir, { recursive: true, force: true });
@@ -12960,7 +13230,7 @@ var ProjectCreator = class {
12960
13230
  return name;
12961
13231
  }
12962
13232
  generateThreadPath(_projectId, threadId) {
12963
- return join17(this.rootFolder, threadId);
13233
+ return join18(this.rootFolder, threadId);
12964
13234
  }
12965
13235
  async *createProjectFromFolder() {
12966
13236
  await loadUtils();
@@ -13110,19 +13380,19 @@ var ProjectCreator = class {
13110
13380
  }
13111
13381
  async findAllPackageJsonFiles(rootPath, currentPath, setupScripts) {
13112
13382
  try {
13113
- await access3(join17(currentPath, "package.json"));
13383
+ await access3(join18(currentPath, "package.json"));
13114
13384
  const relativePath = relative5(rootPath, currentPath);
13115
13385
  let installCommand = "";
13116
13386
  try {
13117
- await access3(join17(currentPath, "yarn.lock"));
13387
+ await access3(join18(currentPath, "yarn.lock"));
13118
13388
  installCommand = "yarn install";
13119
13389
  } catch {
13120
13390
  try {
13121
- await access3(join17(currentPath, "bun.lock"));
13391
+ await access3(join18(currentPath, "bun.lock"));
13122
13392
  installCommand = "bun install";
13123
13393
  } catch {
13124
13394
  try {
13125
- await access3(join17(currentPath, "pnpm-lock.yaml"));
13395
+ await access3(join18(currentPath, "pnpm-lock.yaml"));
13126
13396
  installCommand = "pnpm install";
13127
13397
  } catch {
13128
13398
  installCommand = "npm install";
@@ -13139,7 +13409,7 @@ var ProjectCreator = class {
13139
13409
  try {
13140
13410
  const entries = await readdir8(currentPath);
13141
13411
  for (const entry of entries) {
13142
- const entryPath = join17(currentPath, entry);
13412
+ const entryPath = join18(currentPath, entry);
13143
13413
  try {
13144
13414
  const stats = await stat3(entryPath);
13145
13415
  if (stats.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
@@ -13476,7 +13746,7 @@ var ProjectManagerImpl = class {
13476
13746
  const session = this.terminalSessionManager.getOrCreateSession(threadId, thread.path);
13477
13747
  let workingDir;
13478
13748
  if (cwd) {
13479
- workingDir = cwd.startsWith("/") ? cwd : join18(thread.path, cwd);
13749
+ workingDir = cwd.startsWith("/") ? cwd : join19(thread.path, cwd);
13480
13750
  this.terminalSessionManager.updateWorkingDirectory(threadId, workingDir);
13481
13751
  } else {
13482
13752
  workingDir = session.currentWorkingDirectory;
@@ -13681,9 +13951,9 @@ import { Hono as Hono7 } from "hono";
13681
13951
 
13682
13952
  // src/core/env-manager.ts
13683
13953
  import { promises as fs } from "fs";
13684
- import { join as join19 } from "path";
13954
+ import { join as join20 } from "path";
13685
13955
  async function updateEnvFile(keyNames) {
13686
- const envPath = join19(process.cwd(), ".env");
13956
+ const envPath = join20(process.cwd(), ".env");
13687
13957
  let content = "";
13688
13958
  try {
13689
13959
  content = await fs.readFile(envPath, "utf-8");
@@ -13712,7 +13982,7 @@ async function updateEnvFile(keyNames) {
13712
13982
  await fs.writeFile(envPath, newLines.join("\n") + "\n", "utf-8");
13713
13983
  }
13714
13984
  async function readEnvFile() {
13715
- const envPath = join19(process.cwd(), ".env");
13985
+ const envPath = join20(process.cwd(), ".env");
13716
13986
  const envMap = {};
13717
13987
  try {
13718
13988
  const content = await fs.readFile(envPath, "utf-8");
@@ -14182,8 +14452,8 @@ function createScaffoldRoutes(projectManager) {
14182
14452
  }
14183
14453
 
14184
14454
  // src/features/slash-commands/slash-commands.manager.ts
14185
- import { readdir as readdir9, readFile as readFile11, mkdir as mkdir3, writeFile as writeFile3, unlink as unlink2 } from "fs/promises";
14186
- import { join as join20, basename as basename3, extname as extname4 } from "path";
14455
+ import { readdir as readdir9, readFile as readFile12, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
14456
+ import { join as join21, basename as basename3, extname as extname4 } from "path";
14187
14457
  import { existsSync as existsSync13 } from "fs";
14188
14458
  import { homedir as homedir7 } from "os";
14189
14459
  function slugify(filename) {
@@ -14229,16 +14499,32 @@ function parseFrontmatter3(markdown) {
14229
14499
  return { metadata, content };
14230
14500
  }
14231
14501
  function getGlobalCommandsDir() {
14232
- return join20(homedir7(), ".agents", "commands");
14502
+ return join21(homedir7(), ".agents", "commands");
14233
14503
  }
14234
14504
  function getProjectCommandsDir(threadPath) {
14235
- return join20(threadPath, ".agents", "commands");
14505
+ return join21(threadPath, ".agents", "commands");
14236
14506
  }
14237
14507
  var SlashCommandManager = class _SlashCommandManager {
14238
14508
  /**
14239
14509
  * Built-in commands that cannot be overridden
14240
14510
  */
14241
- static BUILT_IN_COMMANDS = /* @__PURE__ */ new Set(["init"]);
14511
+ static BUILT_IN_COMMANDS = /* @__PURE__ */ new Set(["init", "ralph"]);
14512
+ /**
14513
+ * Built-in command definitions injected programmatically
14514
+ */
14515
+ static BUILT_IN_COMMAND_DEFS = [
14516
+ {
14517
+ name: "ralph",
14518
+ content: "Start the Ralph autonomous loop. Add your stories as todo items, then Ralph will implement them one by one with fresh context between iterations. Each story should be small enough to complete in one iteration.",
14519
+ metadata: {
14520
+ description: "Start the Ralph autonomous implementation loop",
14521
+ mode: "ralph"
14522
+ },
14523
+ scope: "global",
14524
+ filePath: "(built-in)",
14525
+ isBuiltIn: true
14526
+ }
14527
+ ];
14242
14528
  /**
14243
14529
  * Load all available commands for a given thread
14244
14530
  * Project commands override global ones with the same name
@@ -14262,6 +14548,11 @@ var SlashCommandManager = class _SlashCommandManager {
14262
14548
  }
14263
14549
  }
14264
14550
  }
14551
+ for (const builtIn of _SlashCommandManager.BUILT_IN_COMMAND_DEFS) {
14552
+ if (!commands.has(builtIn.name)) {
14553
+ commands.set(builtIn.name, builtIn);
14554
+ }
14555
+ }
14265
14556
  return Array.from(commands.values());
14266
14557
  }
14267
14558
  /**
@@ -14273,8 +14564,8 @@ var SlashCommandManager = class _SlashCommandManager {
14273
14564
  const files = await readdir9(dir);
14274
14565
  for (const file of files) {
14275
14566
  if (!file.endsWith(".md")) continue;
14276
- const filePath = join20(dir, file);
14277
- const fileContent = await readFile11(filePath, "utf-8");
14567
+ const filePath = join21(dir, file);
14568
+ const fileContent = await readFile12(filePath, "utf-8");
14278
14569
  const { metadata, content } = parseFrontmatter3(fileContent);
14279
14570
  const nameWithoutExt = basename3(file, extname4(file));
14280
14571
  const commandName = slugify(nameWithoutExt);
@@ -14308,7 +14599,7 @@ var SlashCommandManager = class _SlashCommandManager {
14308
14599
  await mkdir3(dir, { recursive: true });
14309
14600
  }
14310
14601
  const filename = `${name}.md`;
14311
- const filePath = join20(dir, filename);
14602
+ const filePath = join21(dir, filename);
14312
14603
  if (existsSync13(filePath)) {
14313
14604
  throw new Error(`Command already exists: ${name}`);
14314
14605
  }
@@ -14330,7 +14621,7 @@ var SlashCommandManager = class _SlashCommandManager {
14330
14621
  markdown += "---\n\n";
14331
14622
  }
14332
14623
  markdown += content;
14333
- await writeFile3(filePath, markdown, "utf-8");
14624
+ await writeFile4(filePath, markdown, "utf-8");
14334
14625
  return {
14335
14626
  name,
14336
14627
  content,
@@ -14364,7 +14655,7 @@ var SlashCommandManager = class _SlashCommandManager {
14364
14655
  markdown += "---\n\n";
14365
14656
  }
14366
14657
  markdown += content;
14367
- await writeFile3(filePath, markdown, "utf-8");
14658
+ await writeFile4(filePath, markdown, "utf-8");
14368
14659
  }
14369
14660
  /**
14370
14661
  * Delete a command
@@ -14903,7 +15194,7 @@ function createRuleRoutes(router, projectManager) {
14903
15194
  // src/features/threads/threads.manager.ts
14904
15195
  init_utils();
14905
15196
  import { randomUUID as randomUUID8 } from "crypto";
14906
- import { join as join21 } from "path";
15197
+ import { join as join22 } from "path";
14907
15198
  import { execSync as execSync3 } from "child_process";
14908
15199
  import { rm as rm4, stat as stat4, mkdir as mkdir4 } from "fs/promises";
14909
15200
  init_database();
@@ -15307,7 +15598,7 @@ var ThreadManagerImpl = class {
15307
15598
  * - 7.4 - THE CLI SHALL ensure each Thread clone is stored in a unique directory path
15308
15599
  */
15309
15600
  generateThreadPath(_projectId, threadId) {
15310
- return join21(this.rootFolder, threadId);
15601
+ return join22(this.rootFolder, threadId);
15311
15602
  }
15312
15603
  /**
15313
15604
  * Generates a thread title if not provided
@@ -15500,9 +15791,9 @@ async function handleDeleteThread(c, threadManager) {
15500
15791
  }
15501
15792
 
15502
15793
  // src/core/project-inspector.ts
15503
- import { readFile as readFile12, readdir as readdir10 } from "fs/promises";
15794
+ import { readFile as readFile13, readdir as readdir10 } from "fs/promises";
15504
15795
  import { existsSync as existsSync14 } from "fs";
15505
- import { join as join22, basename as basename4, relative as relative6 } from "path";
15796
+ import { join as join23, basename as basename4, relative as relative6 } from "path";
15506
15797
  import { glob as glob2 } from "glob";
15507
15798
 
15508
15799
  // src/features/project-scripts/project-scripts.database.ts
@@ -15546,13 +15837,13 @@ async function inspectProject(projectPath, projectId) {
15546
15837
  return scripts;
15547
15838
  }
15548
15839
  async function detectPackageManager2(projectPath) {
15549
- if (existsSync14(join22(projectPath, "bun.lockb")) || existsSync14(join22(projectPath, "bun.lock"))) {
15840
+ if (existsSync14(join23(projectPath, "bun.lockb")) || existsSync14(join23(projectPath, "bun.lock"))) {
15550
15841
  return "bun";
15551
15842
  }
15552
- if (existsSync14(join22(projectPath, "pnpm-lock.yaml"))) {
15843
+ if (existsSync14(join23(projectPath, "pnpm-lock.yaml"))) {
15553
15844
  return "pnpm";
15554
15845
  }
15555
- if (existsSync14(join22(projectPath, "yarn.lock"))) {
15846
+ if (existsSync14(join23(projectPath, "yarn.lock"))) {
15556
15847
  return "yarn";
15557
15848
  }
15558
15849
  try {
@@ -15571,8 +15862,8 @@ async function detectPackageManager2(projectPath) {
15571
15862
  return "npm";
15572
15863
  }
15573
15864
  async function detectMonoRepoType(projectPath) {
15574
- const hasPnpmWorkspace = existsSync14(join22(projectPath, "pnpm-workspace.yaml"));
15575
- if (existsSync14(join22(projectPath, "nx.json"))) {
15865
+ const hasPnpmWorkspace = existsSync14(join23(projectPath, "pnpm-workspace.yaml"));
15866
+ if (existsSync14(join23(projectPath, "nx.json"))) {
15576
15867
  return "nx";
15577
15868
  }
15578
15869
  const pkg = await readPackageJson(projectPath);
@@ -15586,10 +15877,10 @@ async function detectMonoRepoType(projectPath) {
15586
15877
  if (hasPnpmWorkspace) {
15587
15878
  return "pnpm";
15588
15879
  }
15589
- if (existsSync14(join22(projectPath, "lerna.json"))) {
15880
+ if (existsSync14(join23(projectPath, "lerna.json"))) {
15590
15881
  return "lerna";
15591
15882
  }
15592
- if (existsSync14(join22(projectPath, "turbo.json"))) {
15883
+ if (existsSync14(join23(projectPath, "turbo.json"))) {
15593
15884
  return "turbo";
15594
15885
  }
15595
15886
  const folderWorkspaces = await detectFolderBasedWorkspaces(projectPath);
@@ -15630,8 +15921,8 @@ async function resolvePackageJsonWorkspaces(projectPath) {
15630
15921
  });
15631
15922
  const workspaces = [];
15632
15923
  for (const folder of folders) {
15633
- const absPath = join22(projectPath, folder);
15634
- if (existsSync14(join22(absPath, "package.json"))) {
15924
+ const absPath = join23(projectPath, folder);
15925
+ if (existsSync14(join23(absPath, "package.json"))) {
15635
15926
  workspaces.push({
15636
15927
  name: basename4(folder),
15637
15928
  folder: absPath,
@@ -15646,12 +15937,12 @@ async function resolvePackageJsonWorkspaces(projectPath) {
15646
15937
  return workspaces;
15647
15938
  }
15648
15939
  async function resolvePnpmWorkspaces(projectPath) {
15649
- const yamlPath = join22(projectPath, "pnpm-workspace.yaml");
15940
+ const yamlPath = join23(projectPath, "pnpm-workspace.yaml");
15650
15941
  if (!existsSync14(yamlPath)) {
15651
15942
  return [{ name: "root", folder: projectPath, relativePath: "." }];
15652
15943
  }
15653
15944
  try {
15654
- const yaml = await readFile12(yamlPath, "utf-8");
15945
+ const yaml = await readFile13(yamlPath, "utf-8");
15655
15946
  const patterns = [];
15656
15947
  for (const line of yaml.split("\n")) {
15657
15948
  const trimmed = line.trim();
@@ -15672,8 +15963,8 @@ async function resolvePnpmWorkspaces(projectPath) {
15672
15963
  });
15673
15964
  const workspaces = [];
15674
15965
  for (const folder of folders) {
15675
- const absPath = join22(projectPath, folder);
15676
- if (existsSync14(join22(absPath, "package.json"))) {
15966
+ const absPath = join23(projectPath, folder);
15967
+ if (existsSync14(join23(absPath, "package.json"))) {
15677
15968
  const wsPkg = await readPackageJson(absPath);
15678
15969
  workspaces.push({
15679
15970
  name: wsPkg?.name ?? basename4(folder),
@@ -15692,12 +15983,12 @@ async function resolvePnpmWorkspaces(projectPath) {
15692
15983
  }
15693
15984
  }
15694
15985
  async function resolveLernaWorkspaces(projectPath) {
15695
- const lernaPath = join22(projectPath, "lerna.json");
15986
+ const lernaPath = join23(projectPath, "lerna.json");
15696
15987
  if (!existsSync14(lernaPath)) {
15697
15988
  return [{ name: "root", folder: projectPath, relativePath: "." }];
15698
15989
  }
15699
15990
  try {
15700
- const content = await readFile12(lernaPath, "utf-8");
15991
+ const content = await readFile13(lernaPath, "utf-8");
15701
15992
  const lerna = JSON.parse(content);
15702
15993
  const patterns = lerna.packages ?? ["packages/*"];
15703
15994
  const folders = await glob2(patterns, {
@@ -15706,8 +15997,8 @@ async function resolveLernaWorkspaces(projectPath) {
15706
15997
  });
15707
15998
  const workspaces = [];
15708
15999
  for (const folder of folders) {
15709
- const absPath = join22(projectPath, folder);
15710
- if (existsSync14(join22(absPath, "package.json"))) {
16000
+ const absPath = join23(projectPath, folder);
16001
+ if (existsSync14(join23(absPath, "package.json"))) {
15711
16002
  const wsPkg = await readPackageJson(absPath);
15712
16003
  workspaces.push({
15713
16004
  name: wsPkg?.name ?? basename4(folder),
@@ -15727,17 +16018,17 @@ async function resolveLernaWorkspaces(projectPath) {
15727
16018
  }
15728
16019
  async function resolveNxWorkspaces(projectPath) {
15729
16020
  const workspaces = [];
15730
- const workspaceJsonPath = join22(projectPath, "workspace.json");
16021
+ const workspaceJsonPath = join23(projectPath, "workspace.json");
15731
16022
  if (existsSync14(workspaceJsonPath)) {
15732
16023
  try {
15733
- const content = await readFile12(workspaceJsonPath, "utf-8");
16024
+ const content = await readFile13(workspaceJsonPath, "utf-8");
15734
16025
  const wsJson = JSON.parse(content);
15735
16026
  for (const [name, value] of Object.entries(wsJson.projects ?? {})) {
15736
16027
  const folder = typeof value === "string" ? value : value.root;
15737
16028
  if (folder) {
15738
16029
  workspaces.push({
15739
16030
  name,
15740
- folder: join22(projectPath, folder),
16031
+ folder: join23(projectPath, folder),
15741
16032
  relativePath: folder
15742
16033
  });
15743
16034
  }
@@ -15752,13 +16043,13 @@ async function resolveNxWorkspaces(projectPath) {
15752
16043
  });
15753
16044
  for (const file of projectJsonFiles) {
15754
16045
  try {
15755
- const content = await readFile12(join22(projectPath, file), "utf-8");
16046
+ const content = await readFile13(join23(projectPath, file), "utf-8");
15756
16047
  const project = JSON.parse(content);
15757
16048
  if (project.name) {
15758
16049
  const folder = file.replace(/\/project\.json$/, "");
15759
16050
  workspaces.push({
15760
16051
  name: project.name,
15761
- folder: join22(projectPath, folder),
16052
+ folder: join23(projectPath, folder),
15762
16053
  relativePath: folder
15763
16054
  });
15764
16055
  }
@@ -15766,15 +16057,15 @@ async function resolveNxWorkspaces(projectPath) {
15766
16057
  }
15767
16058
  }
15768
16059
  if (workspaces.length > 0) return workspaces;
15769
- const appsDir = join22(projectPath, "apps");
16060
+ const appsDir = join23(projectPath, "apps");
15770
16061
  if (existsSync14(appsDir)) {
15771
16062
  const entries = await readdir10(appsDir, { withFileTypes: true });
15772
16063
  for (const entry of entries) {
15773
16064
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
15774
- const folder = join22("apps", entry.name);
16065
+ const folder = join23("apps", entry.name);
15775
16066
  workspaces.push({
15776
16067
  name: entry.name,
15777
- folder: join22(projectPath, folder),
16068
+ folder: join23(projectPath, folder),
15778
16069
  relativePath: folder
15779
16070
  });
15780
16071
  }
@@ -15808,13 +16099,13 @@ async function detectFolderBasedWorkspaces(projectPath) {
15808
16099
  const results = [];
15809
16100
  for (const entry of entries) {
15810
16101
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules" && entry.name !== "dist" && entry.name !== "build") {
15811
- const pkgPath = join22(projectPath, entry.name, "package.json");
16102
+ const pkgPath = join23(projectPath, entry.name, "package.json");
15812
16103
  if (existsSync14(pkgPath)) {
15813
16104
  try {
15814
- const content = await readFile12(pkgPath, "utf-8");
16105
+ const content = await readFile13(pkgPath, "utf-8");
15815
16106
  const pkg = JSON.parse(content);
15816
16107
  if (pkg.dependencies || pkg.devDependencies || pkg.scripts) {
15817
- results.push({ name: entry.name, absPath: join22(projectPath, entry.name) });
16108
+ results.push({ name: entry.name, absPath: join23(projectPath, entry.name) });
15818
16109
  }
15819
16110
  } catch {
15820
16111
  }
@@ -15870,7 +16161,7 @@ function pmRunCommand(packageManager, scriptName) {
15870
16161
  }
15871
16162
  async function readPackageJson(dir) {
15872
16163
  try {
15873
- const content = await readFile12(join22(dir, "package.json"), "utf-8");
16164
+ const content = await readFile13(join23(dir, "package.json"), "utf-8");
15874
16165
  return JSON.parse(content);
15875
16166
  } catch {
15876
16167
  return null;
@@ -16091,8 +16382,8 @@ async function handleListThreadFiles(c, threadManager) {
16091
16382
  }
16092
16383
 
16093
16384
  // src/features/threads/threads-explorer.route.ts
16094
- import { readdir as readdir11, readFile as readFile13, rename as rename2, writeFile as writeFile4, mkdir as mkdir5, rm as rm5, access as access4 } from "fs/promises";
16095
- import { join as join23 } from "path";
16385
+ import { readdir as readdir11, readFile as readFile14, rename as rename2, writeFile as writeFile5, mkdir as mkdir5, rm as rm5, access as access4 } from "fs/promises";
16386
+ import { join as join24 } from "path";
16096
16387
  import { spawn as spawn2 } from "child_process";
16097
16388
  var Utils3 = null;
16098
16389
  var utilsLoaded3 = false;
@@ -16155,7 +16446,7 @@ async function handleListThreadExplorerDir(c, threadManager) {
16155
16446
  const entries = dirents.filter((d) => !(isRoot && d.name === ".git")).map((d) => ({
16156
16447
  name: d.name,
16157
16448
  type: d.isDirectory() ? "folder" : "file",
16158
- path: queryPath ? join23(queryPath, d.name) : d.name
16449
+ path: queryPath ? join24(queryPath, d.name) : d.name
16159
16450
  })).sort((a, b) => {
16160
16451
  if (a.type !== b.type) return a.type === "folder" ? -1 : 1;
16161
16452
  return a.name.localeCompare(b.name);
@@ -16301,7 +16592,7 @@ async function handleCreateExplorerFile(c, threadManager) {
16301
16592
  absParent = validated;
16302
16593
  }
16303
16594
  await mkdir5(absParent, { recursive: true });
16304
- await writeFile4(join23(absParent, name), "", "utf-8");
16595
+ await writeFile5(join24(absParent, name), "", "utf-8");
16305
16596
  const relPath = dirPath ? `${dirPath}/${name}` : name;
16306
16597
  return c.json({ success: true, path: relPath });
16307
16598
  } catch (error) {
@@ -16358,7 +16649,7 @@ async function handleGetExplorerMedia(c, threadManager) {
16358
16649
  }
16359
16650
  const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
16360
16651
  const contentType = MEDIA_MIME_TYPES[ext] ?? "application/octet-stream";
16361
- const data = await readFile13(absPath);
16652
+ const data = await readFile14(absPath);
16362
16653
  return new Response(data, { headers: { "Content-Type": contentType } });
16363
16654
  } catch (error) {
16364
16655
  return errorResponse(
@@ -16403,7 +16694,7 @@ async function handleCreateExplorerFolder(c, threadManager) {
16403
16694
  }
16404
16695
  absParent = validated;
16405
16696
  }
16406
- await mkdir5(join23(absParent, name), { recursive: true });
16697
+ await mkdir5(join24(absParent, name), { recursive: true });
16407
16698
  const relPath = dirPath ? `${dirPath}/${name}` : name;
16408
16699
  return c.json({ success: true, path: relPath });
16409
16700
  } catch (error) {
@@ -16513,14 +16804,14 @@ async function handleOpenThread(c, threadManager) {
16513
16804
  }
16514
16805
 
16515
16806
  // src/features/threads/threads-conversation-folder-path.route.ts
16516
- import { join as join24 } from "path";
16807
+ import { join as join25 } from "path";
16517
16808
  async function handleGetConversationFolderPath(c) {
16518
16809
  try {
16519
16810
  const threadId = c.req.param("id");
16520
16811
  if (!threadId) {
16521
16812
  return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Thread ID is required", 400);
16522
16813
  }
16523
- const path6 = join24(getDataDir(), threadId);
16814
+ const path6 = join25(getDataDir(), threadId);
16524
16815
  return successResponse(c, { path: path6 });
16525
16816
  } catch (error) {
16526
16817
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -16574,13 +16865,13 @@ async function handleFixComments(c, threadManager) {
16574
16865
  }
16575
16866
 
16576
16867
  // src/features/threads/threads-ai-files.route.ts
16577
- import { readFile as readFile14, writeFile as writeFile6, mkdir as mkdir7, access as access5, rm as rm6 } from "fs/promises";
16578
- import { join as join26 } from "path";
16868
+ import { readFile as readFile15, writeFile as writeFile7, mkdir as mkdir7, access as access5, rm as rm6 } from "fs/promises";
16869
+ import { join as join27 } from "path";
16579
16870
  import { existsSync as existsSync15 } from "fs";
16580
16871
 
16581
16872
  // src/features/git/git-download-folder.ts
16582
- import { mkdir as mkdir6, writeFile as writeFile5 } from "fs/promises";
16583
- import { join as join25, dirname as dirname4 } from "path";
16873
+ import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
16874
+ import { join as join26, dirname as dirname4 } from "path";
16584
16875
  async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
16585
16876
  const { token, ref } = options;
16586
16877
  const match = repoUrl.replace(/\.git$/, "").match(/github\.com\/([^/]+)\/([^/]+)/);
@@ -16616,7 +16907,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
16616
16907
  await Promise.all(
16617
16908
  files.map(async (file) => {
16618
16909
  const relativePath = file.path.slice(prefix.length);
16619
- const localPath = join25(destPath, relativePath);
16910
+ const localPath = join26(destPath, relativePath);
16620
16911
  await mkdir6(dirname4(localPath), { recursive: true });
16621
16912
  const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${refParam}/${file.path}`;
16622
16913
  const fileRes = await fetch(rawUrl, { headers });
@@ -16625,7 +16916,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
16625
16916
  }
16626
16917
  let content = Buffer.from(await fileRes.arrayBuffer()).toString("utf8");
16627
16918
  content = content.replace(/Claude/gi, "Tarsk");
16628
- await writeFile5(localPath, content, "utf8");
16919
+ await writeFile6(localPath, content, "utf8");
16629
16920
  console.log(` \u2713 ${relativePath}`);
16630
16921
  })
16631
16922
  );
@@ -16719,7 +17010,7 @@ Links to important documentation, tools, or references.
16719
17010
  } catch {
16720
17011
  return c.json({ error: { code: "NOT_FOUND", message: `File not found: ${filePath}` } }, 404);
16721
17012
  }
16722
- const content = await readFile14(absPath, "utf-8");
17013
+ const content = await readFile15(absPath, "utf-8");
16723
17014
  return c.json({ content, path: filePath });
16724
17015
  } catch (error) {
16725
17016
  return errorResponse(
@@ -16758,9 +17049,9 @@ async function handleSaveThreadAIFile(c, threadManager) {
16758
17049
  if (!absPath) {
16759
17050
  return c.json({ error: { code: "BAD_REQUEST", message: "Invalid file path" } }, 400);
16760
17051
  }
16761
- const parentDir = join26(absPath, "..");
17052
+ const parentDir = join27(absPath, "..");
16762
17053
  await mkdir7(parentDir, { recursive: true });
16763
- await writeFile6(absPath, content, "utf-8");
17054
+ await writeFile7(absPath, content, "utf-8");
16764
17055
  return c.json({ success: true, path: filePath });
16765
17056
  } catch (error) {
16766
17057
  return errorResponse(
@@ -16847,10 +17138,10 @@ async function handleCreateThreadAgent(c, threadManager) {
16847
17138
  if (!thread) {
16848
17139
  return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
16849
17140
  }
16850
- const agentRelPath = join26(".agents", "agents", name);
16851
- const agentAbsPath = join26(thread.path, agentRelPath);
16852
- const agentFileRelPath = join26(agentRelPath, "AGENT.md");
16853
- const agentFileAbsPath = join26(agentAbsPath, "AGENT.md");
17141
+ const agentRelPath = join27(".agents", "agents", name);
17142
+ const agentAbsPath = join27(thread.path, agentRelPath);
17143
+ const agentFileRelPath = join27(agentRelPath, "AGENT.md");
17144
+ const agentFileAbsPath = join27(agentAbsPath, "AGENT.md");
16854
17145
  if (existsSync15(agentAbsPath)) {
16855
17146
  return c.json(
16856
17147
  { error: { code: "CONFLICT", message: `Agent '${name}' already exists` } },
@@ -16867,7 +17158,7 @@ description: ${description}${toolsLine}
16867
17158
 
16868
17159
  Place your agent system prompt here as markdown. This will be used as the system prompt when this agent is invoked as a subagent.
16869
17160
  `;
16870
- await writeFile6(agentFileAbsPath, agentContent, "utf-8");
17161
+ await writeFile7(agentFileAbsPath, agentContent, "utf-8");
16871
17162
  return c.json({
16872
17163
  success: true,
16873
17164
  path: agentFileRelPath,
@@ -16917,10 +17208,10 @@ async function handleCreateThreadSkill(c, threadManager) {
16917
17208
  if (!thread) {
16918
17209
  return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
16919
17210
  }
16920
- const skillRelPath = join26(".agents", "skills", name);
16921
- const skillAbsPath = join26(thread.path, skillRelPath);
16922
- const skillFileRelPath = join26(skillRelPath, "SKILL.md");
16923
- const skillFileAbsPath = join26(skillAbsPath, "SKILL.md");
17211
+ const skillRelPath = join27(".agents", "skills", name);
17212
+ const skillAbsPath = join27(thread.path, skillRelPath);
17213
+ const skillFileRelPath = join27(skillRelPath, "SKILL.md");
17214
+ const skillFileAbsPath = join27(skillAbsPath, "SKILL.md");
16924
17215
  if (existsSync15(skillAbsPath)) {
16925
17216
  return c.json(
16926
17217
  { error: { code: "CONFLICT", message: `Skill '${name}' already exists` } },
@@ -16935,7 +17226,7 @@ description: ${description}
16935
17226
 
16936
17227
  Place your skill instructions here as markdown. This will be used when Tarsk is prompted with a request related to the description of this skill.
16937
17228
  `;
16938
- await writeFile6(skillFileAbsPath, skillContent, "utf-8");
17229
+ await writeFile7(skillFileAbsPath, skillContent, "utf-8");
16939
17230
  return c.json({
16940
17231
  success: true,
16941
17232
  path: skillFileRelPath,
@@ -17604,7 +17895,7 @@ import { existsSync as existsSync17 } from "fs";
17604
17895
  // src/features/git/git.utils.ts
17605
17896
  init_utils();
17606
17897
  import { existsSync as existsSync16, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
17607
- import { isAbsolute as isAbsolute3, normalize as normalize2, resolve as resolve3, join as join27 } from "path";
17898
+ import { isAbsolute as isAbsolute3, normalize as normalize2, resolve as resolve3, join as join28 } from "path";
17608
17899
  import { completeSimple } from "@mariozechner/pi-ai";
17609
17900
  async function resolveModelAndKey(provider, modelId, metadataManager) {
17610
17901
  const providerConfig = await resolveProviderConfig(provider);
@@ -17861,7 +18152,7 @@ async function getUntrackedFilesDiff(gitRoot) {
17861
18152
  const parts = [];
17862
18153
  const maxFileSize = 1e5;
17863
18154
  for (const relPath of untrackedPaths) {
17864
- const fullPath = join27(gitRoot, relPath);
18155
+ const fullPath = join28(gitRoot, relPath);
17865
18156
  if (!existsSync16(fullPath)) continue;
17866
18157
  try {
17867
18158
  if (statSync4(fullPath).isDirectory()) continue;
@@ -20230,8 +20521,8 @@ function createUpdateRoutes(updater) {
20230
20521
 
20231
20522
  // src/features/mcp/mcp.routes.ts
20232
20523
  import { Hono as Hono13 } from "hono";
20233
- import { readFile as readFile15, writeFile as writeFile7, mkdir as mkdir9, access as access6 } from "fs/promises";
20234
- import { join as join28, dirname as dirname6 } from "path";
20524
+ import { readFile as readFile16, writeFile as writeFile8, mkdir as mkdir9, access as access6 } from "fs/promises";
20525
+ import { join as join29, dirname as dirname6 } from "path";
20235
20526
 
20236
20527
  // src/features/mcp/mcp.popular.json
20237
20528
  var mcp_popular_default = [
@@ -20342,10 +20633,10 @@ var mcp_popular_default = [
20342
20633
  var MCP_CONFIG_PATHS2 = [".agents/mcp.json", "mcp.json"];
20343
20634
  async function readMCPConfig(projectPath) {
20344
20635
  for (const configPath of MCP_CONFIG_PATHS2) {
20345
- const fullPath = join28(projectPath, configPath);
20636
+ const fullPath = join29(projectPath, configPath);
20346
20637
  try {
20347
20638
  await access6(fullPath);
20348
- const content = await readFile15(fullPath, "utf-8");
20639
+ const content = await readFile16(fullPath, "utf-8");
20349
20640
  const config = JSON.parse(content);
20350
20641
  return { config, filePath: fullPath };
20351
20642
  } catch {
@@ -20356,9 +20647,9 @@ async function readMCPConfig(projectPath) {
20356
20647
  }
20357
20648
  async function writeMCPConfig(projectPath, config) {
20358
20649
  const existing = await readMCPConfig(projectPath);
20359
- const targetPath = existing?.filePath ?? join28(projectPath, ".agents/mcp.json");
20650
+ const targetPath = existing?.filePath ?? join29(projectPath, ".agents/mcp.json");
20360
20651
  await mkdir9(dirname6(targetPath), { recursive: true });
20361
- await writeFile7(targetPath, JSON.stringify(config, null, 2), "utf-8");
20652
+ await writeFile8(targetPath, JSON.stringify(config, null, 2), "utf-8");
20362
20653
  }
20363
20654
  function createMCPRoutes() {
20364
20655
  const router = new Hono13();
@@ -21030,7 +21321,7 @@ async function clipboardWriteImage(c) {
21030
21321
  }
21031
21322
 
21032
21323
  // src/features/image/image-save.route.ts
21033
- import { join as join29 } from "path";
21324
+ import { join as join30 } from "path";
21034
21325
  var Utils5 = null;
21035
21326
  var utilsLoaded5 = false;
21036
21327
  async function loadUtils5() {
@@ -21067,7 +21358,7 @@ async function saveImage(c) {
21067
21358
  return c.json({ error: "imageUrl or imageData is required" }, 400);
21068
21359
  }
21069
21360
  const name = filename ?? `generated-image-${Date.now()}.png`;
21070
- const savePath = join29(Utils5.paths.downloads, name);
21361
+ const savePath = join30(Utils5.paths.downloads, name);
21071
21362
  await Bun.write(savePath, data);
21072
21363
  return c.json({ success: true, path: savePath });
21073
21364
  } catch (error) {