tarsk 0.4.17 → 0.4.19

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 (45) hide show
  1. package/dist/index.js +660 -253
  2. package/dist/public/assets/{account-view-Ct1PS3qy.js → account-view-Bbhl_jIc.js} +1 -1
  3. package/dist/public/assets/{api-CKm8861D.js → api-B28NOxKR.js} +1 -1
  4. package/dist/public/assets/{browser-tab-C_Iqe3Vc.js → browser-tab-os7ReSAI.js} +1 -1
  5. package/dist/public/assets/{context-menu-3kNmHEtL.js → context-menu-Bs2ogSXU.js} +1 -1
  6. package/dist/public/assets/conversation-history-view-DjyJh65Y.js +1 -0
  7. package/dist/public/assets/dialogs-config-D3xml4W2.js +51 -0
  8. package/dist/public/assets/diff-view-702Bpg9H.js +3 -0
  9. package/dist/public/assets/explorer-tab-view-BvmxzUAs.js +2 -0
  10. package/dist/public/assets/explorer-tree-mCR_Wwfz.js +1 -0
  11. package/dist/public/assets/{explorer-view-CIHVPut6.js → explorer-view-BN9RAXjq.js} +1 -1
  12. package/dist/public/assets/history-view-BnRtfSte.js +1 -0
  13. package/dist/public/assets/index-BQ391mCU.css +1 -0
  14. package/dist/public/assets/index-CSGl7zru.js +51 -0
  15. package/dist/public/assets/onboarding-dialog-Ic62dkfi.js +1 -0
  16. package/dist/public/assets/onboarding-eQed-ZzK.js +1 -0
  17. package/dist/public/assets/project-settings-view-BsGGUbat.js +1 -0
  18. package/dist/public/assets/{provider-details-view-CqOZhRbJ.js → provider-details-view-Di1Hw9Bi.js} +1 -1
  19. package/dist/public/assets/providers-sidebar-klkFVN5y.js +1 -0
  20. package/dist/public/assets/{react-vendor-CdgKcH5K.js → react-vendor-DcsTuVun.js} +5 -5
  21. package/dist/public/assets/{settings-view-CZRZDm9N.js → settings-view-CF-gBfNl.js} +2 -2
  22. package/dist/public/assets/store-erJP4KTe.js +2 -0
  23. package/dist/public/assets/{tab-context-YIVEeWqD.js → tab-context-CnzSKG0L.js} +1 -1
  24. package/dist/public/assets/{terminal-panel-fvpKl9qj.js → terminal-panel-BhJSKgOW.js} +2 -2
  25. package/dist/public/assets/{textarea-CX-t8DNp.js → textarea-C1e4J_5m.js} +1 -1
  26. package/dist/public/assets/todos-view-BtDv09d6.js +1 -0
  27. package/dist/public/assets/{use-toast-Dpl0D_FP.js → use-toast-BZ7ir6gB.js} +1 -1
  28. package/dist/public/assets/{utils-B6DpTHZi.js → utils-CLxr_0Ql.js} +1 -1
  29. package/dist/public/index.html +11 -11
  30. package/dist/scaffold-templates.json +200 -44
  31. package/package.json +1 -1
  32. package/dist/public/assets/conversation-history-view-Bk1wydkA.js +0 -1
  33. package/dist/public/assets/dialogs-config-DQYl3sMZ.js +0 -46
  34. package/dist/public/assets/diff-view-BqbiT0d1.js +0 -3
  35. package/dist/public/assets/explorer-tab-view-7KsDKtCg.js +0 -2
  36. package/dist/public/assets/explorer-tree-DvAdnMk6.js +0 -1
  37. package/dist/public/assets/history-view-D-rFKy95.js +0 -1
  38. package/dist/public/assets/index-CkjiMXPO.js +0 -49
  39. package/dist/public/assets/index-JT2CTiRC.css +0 -1
  40. package/dist/public/assets/onboarding-CXNcrF0p.js +0 -1
  41. package/dist/public/assets/onboarding-dialog-HeNGG40b.js +0 -1
  42. package/dist/public/assets/project-settings-view-e-rir43e.js +0 -1
  43. package/dist/public/assets/providers-sidebar-DW9GF1go.js +0 -1
  44. package/dist/public/assets/store-DE6QsxYI.js +0 -2
  45. package/dist/public/assets/todos-view-B5EL6lKk.js +0 -1
package/dist/index.js CHANGED
@@ -638,6 +638,13 @@ async function runMigrations(db) {
638
638
  await db.execute(`ALTER TABLE projects ADD COLUMN testPrompt TEXT`);
639
639
  await db.execute(`ALTER TABLE projects ADD COLUMN reviewPrompt TEXT`);
640
640
  }
641
+ const hasValidationScript = projectsInfo.rows.some(
642
+ (col) => col.name === "validationScript"
643
+ );
644
+ if (!hasValidationScript) {
645
+ console.log("[db] Running migration: Adding validationScript column to projects");
646
+ await db.execute(`ALTER TABLE projects ADD COLUMN validationScript TEXT`);
647
+ }
641
648
  const projectScriptsExists = await db.execute(
642
649
  `SELECT name FROM sqlite_master WHERE type='table' AND name='project_scripts'`
643
650
  );
@@ -5701,7 +5708,7 @@ var devServerCache = new DevServerCache();
5701
5708
  // src/agent/agent.prompt-loader.ts
5702
5709
  function buildDefaultPrompt(tools) {
5703
5710
  const toolList = tools.map((t) => t.name).join(", ");
5704
- return `You are a helpful coding assistant. You have access to ${toolList} tools. Use them to explore and modify the codebase as needed. Skills are created and stored in .agents/skills/ . Use Mermaid for flowcharts, sequence diagrams, state diagrams, or graphs when appropriate`;
5711
+ return `You are a helpful coding assistant. You have access to ${toolList} tools. Use them to explore and modify the codebase as needed. Skills are created and stored in .agents/skills/ . Use Mermaid for flowcharts, sequence diagrams, state diagrams, or graphs when appropriate. Do not use emojis.`;
5705
5712
  }
5706
5713
  var PLAN_MODE_INSTRUCTIONS = `
5707
5714
 
@@ -5722,6 +5729,7 @@ You are currently in PLAN MODE. You MUST follow these constraints strictly:
5722
5729
  - Do NOT create, delete, or modify any files
5723
5730
  - Do NOT run any commands that change system state (npm install, git commit, etc.)
5724
5731
  - You MAY use read-only tools (Read, Glob, Grep, Bash with read-only commands like ls, cat, git status, git log, git diff) to explore the codebase
5732
+ - Do NOT use emojis
5725
5733
 
5726
5734
  ## Output format:
5727
5735
  Structure your response as:
@@ -7361,7 +7369,225 @@ async function getAccountInfo(c) {
7361
7369
  }
7362
7370
  }
7363
7371
 
7372
+ // src/features/projects/projects.database.ts
7373
+ async function insertProject(db, project) {
7374
+ try {
7375
+ await db.execute(
7376
+ `
7377
+ INSERT INTO projects (id, name, gitUrl, path, createdAt, openWith, commands, setupScript, validationScript, runCommand, commitMethod, planPrompt, testPrompt, reviewPrompt)
7378
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7379
+ `,
7380
+ [
7381
+ project.id,
7382
+ project.name,
7383
+ project.gitUrl,
7384
+ project.path,
7385
+ project.createdAt.toISOString(),
7386
+ project.openWith ?? null,
7387
+ project.commands ? JSON.stringify(project.commands) : null,
7388
+ project.setupScript ?? null,
7389
+ project.validationScript ?? null,
7390
+ project.runCommand ?? null,
7391
+ project.commitMethod ?? null,
7392
+ project.planPrompt ?? null,
7393
+ project.testPrompt ?? null,
7394
+ project.reviewPrompt ?? null
7395
+ ]
7396
+ );
7397
+ } catch (error) {
7398
+ console.error("Failed to insert project:", error);
7399
+ throw error;
7400
+ }
7401
+ }
7402
+ async function updateProject(db, id, updates) {
7403
+ try {
7404
+ const fields = [];
7405
+ const values = [];
7406
+ if (updates.name !== void 0) {
7407
+ fields.push("name = ?");
7408
+ values.push(updates.name);
7409
+ }
7410
+ if (updates.gitUrl !== void 0) {
7411
+ fields.push("gitUrl = ?");
7412
+ values.push(updates.gitUrl);
7413
+ }
7414
+ if (updates.path !== void 0) {
7415
+ fields.push("path = ?");
7416
+ values.push(updates.path);
7417
+ }
7418
+ if (updates.openWith !== void 0) {
7419
+ fields.push("openWith = ?");
7420
+ values.push(updates.openWith ?? null);
7421
+ }
7422
+ if (updates.commands !== void 0) {
7423
+ fields.push("commands = ?");
7424
+ values.push(updates.commands ? JSON.stringify(updates.commands) : null);
7425
+ }
7426
+ if (updates.setupScript !== void 0) {
7427
+ fields.push("setupScript = ?");
7428
+ values.push(updates.setupScript ?? null);
7429
+ }
7430
+ if (updates.validationScript !== void 0) {
7431
+ fields.push("validationScript = ?");
7432
+ values.push(updates.validationScript ?? null);
7433
+ }
7434
+ if (updates.runCommand !== void 0) {
7435
+ fields.push("runCommand = ?");
7436
+ values.push(updates.runCommand ?? null);
7437
+ }
7438
+ if (updates.commitMethod !== void 0) {
7439
+ fields.push("commitMethod = ?");
7440
+ values.push(updates.commitMethod ?? null);
7441
+ }
7442
+ if (updates.planPrompt !== void 0) {
7443
+ fields.push("planPrompt = ?");
7444
+ values.push(updates.planPrompt ?? null);
7445
+ }
7446
+ if (updates.testPrompt !== void 0) {
7447
+ fields.push("testPrompt = ?");
7448
+ values.push(updates.testPrompt ?? null);
7449
+ }
7450
+ if (updates.reviewPrompt !== void 0) {
7451
+ fields.push("reviewPrompt = ?");
7452
+ values.push(updates.reviewPrompt ?? null);
7453
+ }
7454
+ if (fields.length === 0) {
7455
+ return;
7456
+ }
7457
+ values.push(id);
7458
+ const sql = `UPDATE projects SET ${fields.join(", ")} WHERE id = ?`;
7459
+ await db.execute(sql, values);
7460
+ } catch (error) {
7461
+ console.error("Failed to update project:", error);
7462
+ throw error;
7463
+ }
7464
+ }
7465
+ async function deleteProject(db, id) {
7466
+ try {
7467
+ await db.execute("PRAGMA foreign_keys = OFF");
7468
+ try {
7469
+ await db.execute(
7470
+ `
7471
+ DELETE FROM conversation_history
7472
+ WHERE threadId IN (
7473
+ SELECT threads.id FROM threads WHERE threads.projectId = ?
7474
+ )
7475
+ `,
7476
+ [id]
7477
+ );
7478
+ await db.execute(
7479
+ `
7480
+ DELETE FROM todos
7481
+ WHERE threadId IN (
7482
+ SELECT threads.id FROM threads WHERE threads.projectId = ?
7483
+ )
7484
+ `,
7485
+ [id]
7486
+ );
7487
+ await db.execute("DELETE FROM threads WHERE projectId = ?", [id]);
7488
+ await db.execute("DELETE FROM project_todos WHERE projectId = ?", [id]);
7489
+ await db.execute("DELETE FROM projects WHERE id = ?", [id]);
7490
+ } finally {
7491
+ await db.execute("PRAGMA foreign_keys = ON");
7492
+ }
7493
+ } catch (error) {
7494
+ console.error("Failed to delete project:", error);
7495
+ throw error;
7496
+ }
7497
+ }
7498
+ async function getProject(db, id) {
7499
+ const result = await db.execute("SELECT * FROM projects WHERE id = ?", [id]);
7500
+ const row = result.rows[0];
7501
+ if (!row) {
7502
+ return null;
7503
+ }
7504
+ return deserializeProject(db, row);
7505
+ }
7506
+ async function getAllProjects(db) {
7507
+ try {
7508
+ const result = await db.execute("SELECT * FROM projects ORDER BY createdAt DESC");
7509
+ const rows = result.rows;
7510
+ const threadResult = await db.execute(
7511
+ "SELECT id, projectId FROM threads ORDER BY createdAt DESC"
7512
+ );
7513
+ const threadRows = threadResult.rows;
7514
+ const threadIdsByProject = /* @__PURE__ */ new Map();
7515
+ for (const row of threadRows) {
7516
+ const ids = threadIdsByProject.get(row.projectId);
7517
+ if (ids) {
7518
+ ids.push(row.id);
7519
+ } else {
7520
+ threadIdsByProject.set(row.projectId, [row.id]);
7521
+ }
7522
+ }
7523
+ return rows.map(
7524
+ (row) => deserializeProjectWithThreads(row, threadIdsByProject.get(row.id) ?? [])
7525
+ );
7526
+ } catch (error) {
7527
+ console.error("Failed to get all projects:", error);
7528
+ throw error;
7529
+ }
7530
+ }
7531
+ async function getProjectThreadIds(db, projectId) {
7532
+ const result = await db.execute(
7533
+ "SELECT id FROM threads WHERE projectId = ? ORDER BY createdAt DESC",
7534
+ [projectId]
7535
+ );
7536
+ const rows = result.rows;
7537
+ return rows.map((row) => row.id);
7538
+ }
7539
+ function deserializeProjectWithThreads(row, threadIds) {
7540
+ return {
7541
+ id: row.id,
7542
+ name: row.name,
7543
+ gitUrl: row.gitUrl,
7544
+ path: row.path,
7545
+ createdAt: new Date(row.createdAt),
7546
+ threads: threadIds,
7547
+ openWith: row.openWith ?? void 0,
7548
+ commands: row.commands ? JSON.parse(row.commands) : void 0,
7549
+ setupScript: row.setupScript ?? void 0,
7550
+ validationScript: row.validationScript ?? void 0,
7551
+ runCommand: row.runCommand ?? void 0,
7552
+ commitMethod: row.commitMethod && isValidCommitMethod(row.commitMethod) ? row.commitMethod : void 0,
7553
+ planPrompt: row.planPrompt ?? void 0,
7554
+ testPrompt: row.testPrompt ?? void 0,
7555
+ reviewPrompt: row.reviewPrompt ?? void 0
7556
+ };
7557
+ }
7558
+ async function deserializeProject(db, row) {
7559
+ return deserializeProjectWithThreads(row, await getProjectThreadIds(db, row.id));
7560
+ }
7561
+
7364
7562
  // src/features/chat/chat-post.route.ts
7563
+ init_utils();
7564
+ function runValidationScript(script, cwd) {
7565
+ return new Promise((resolve6) => {
7566
+ const child = spawnShell(script, { cwd, stdio: ["pipe", "pipe", "pipe"] });
7567
+ let output = "";
7568
+ const stdoutDecoder = new TextDecoder();
7569
+ const stderrDecoder = new TextDecoder();
7570
+ child.stdout?.on("data", (data) => {
7571
+ output += stdoutDecoder.decode(data, { stream: true });
7572
+ });
7573
+ child.stderr?.on("data", (data) => {
7574
+ output += stderrDecoder.decode(data, { stream: true });
7575
+ });
7576
+ child.on("close", (code) => {
7577
+ output += stdoutDecoder.decode();
7578
+ output += stderrDecoder.decode();
7579
+ resolve6({ exitCode: code ?? 1, output: output.trim() });
7580
+ });
7581
+ child.on("error", (err) => {
7582
+ output += stdoutDecoder.decode();
7583
+ output += stderrDecoder.decode();
7584
+ resolve6({
7585
+ exitCode: 1,
7586
+ output: `${output}${output ? "\n" : ""}${err.message}`.trim()
7587
+ });
7588
+ });
7589
+ });
7590
+ }
7365
7591
  async function postChatMessage(c, threadManager, agentExecutor, conversationManager, processingStateManager) {
7366
7592
  try {
7367
7593
  const body = await c.req.json();
@@ -7542,36 +7768,127 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
7542
7768
  }
7543
7769
  processingStateManager.setProcessing(threadId);
7544
7770
  processingStateManager.registerAbortController(threadId, abortController);
7771
+ async function* executeAgent(prompt, ctx, signal, history2, captured) {
7772
+ for await (const event of agentExecutor.execute(prompt, ctx, signal, history2)) {
7773
+ captured.events.push(event);
7774
+ if (event.type === "message" && event.content) {
7775
+ captured.fullContent += event.content;
7776
+ }
7777
+ if (event.type === "message" && typeof event.content === "string" && (event.role === "tool" || isToolLikeContent(event.content))) {
7778
+ const thinkingEvent = { type: "thinking", content: event.content };
7779
+ processingStateManager.pushEvent(threadId, thinkingEvent);
7780
+ yield thinkingEvent;
7781
+ continue;
7782
+ }
7783
+ processingStateManager.pushEvent(threadId, event);
7784
+ yield event;
7785
+ }
7786
+ }
7545
7787
  async function* chatExecutionGenerator() {
7546
- const capturedEvents = [];
7547
- let fullContent = "";
7788
+ const captured = { events: [], fullContent: "" };
7548
7789
  try {
7549
- for await (const event of agentExecutor.execute(
7790
+ yield* executeAgent(
7550
7791
  content,
7551
7792
  context,
7552
7793
  abortController.signal,
7553
- conversationHistory
7554
- )) {
7555
- capturedEvents.push(event);
7556
- if (event.type === "message" && event.content) {
7557
- fullContent += event.content;
7558
- }
7559
- if (event.type === "message" && typeof event.content === "string" && (event.role === "tool" || isToolLikeContent(event.content))) {
7560
- const thinkingEvent = { type: "thinking", content: event.content };
7561
- processingStateManager.pushEvent(threadId, thinkingEvent);
7562
- yield thinkingEvent;
7563
- continue;
7564
- }
7565
- processingStateManager.pushEvent(threadId, event);
7566
- yield event;
7567
- }
7568
- const finalContent = extractAssistantContent(capturedEvents, fullContent);
7569
- await conversationManager.completeMessage(messageId, finalContent, capturedEvents);
7794
+ conversationHistory,
7795
+ captured
7796
+ );
7797
+ const finalContent = extractAssistantContent(captured.events, captured.fullContent);
7798
+ await conversationManager.completeMessage(messageId, finalContent, captured.events);
7570
7799
  console.log("[ChatRoute] Conversation captured:", {
7571
7800
  messageId,
7572
- eventCount: capturedEvents.length,
7801
+ eventCount: captured.events.length,
7573
7802
  contentLength: finalContent.length
7574
7803
  });
7804
+ const db = await getDatabase();
7805
+ const project = await getProject(db, thread.projectId);
7806
+ const validationScript = project?.validationScript;
7807
+ if (validationScript) {
7808
+ const MAX_VALIDATION_ATTEMPTS = 10;
7809
+ let attempt = 0;
7810
+ conversationHistory.push({ userMessage: content, assistantResponse: finalContent });
7811
+ while (attempt < MAX_VALIDATION_ATTEMPTS) {
7812
+ if (abortController.signal.aborted) break;
7813
+ console.log(`[ChatRoute] Running validation script (attempt ${attempt + 1})`);
7814
+ const runningEvent = {
7815
+ type: "thinking",
7816
+ content: `[Validating] Running validation script (attempt ${attempt + 1}):
7817
+ $ ${validationScript}`
7818
+ };
7819
+ processingStateManager.pushEvent(threadId, runningEvent);
7820
+ yield runningEvent;
7821
+ const result = await runValidationScript(validationScript, threadPath);
7822
+ if (result.exitCode === 0) {
7823
+ console.log("[ChatRoute] Validation script passed");
7824
+ const passEvent = {
7825
+ type: "thinking",
7826
+ content: `[Validating] Validation passed (exit code 0)${result.output ? `
7827
+ ${result.output}` : ""}`
7828
+ };
7829
+ processingStateManager.pushEvent(threadId, passEvent);
7830
+ yield passEvent;
7831
+ break;
7832
+ }
7833
+ console.log(`[ChatRoute] Validation script failed (exit code ${result.exitCode})`);
7834
+ const failEvent = {
7835
+ type: "thinking",
7836
+ content: `[Validating] Validation failed (exit code ${result.exitCode})
7837
+ ${result.output}`
7838
+ };
7839
+ processingStateManager.pushEvent(threadId, failEvent);
7840
+ yield failEvent;
7841
+ attempt++;
7842
+ const validationPrompt = `The validation script failed with exit code ${result.exitCode}. Please fix the issues and try again.
7843
+
7844
+ Validation script: ${validationScript}
7845
+
7846
+ Output:
7847
+ ${result.output}`;
7848
+ const updatedHistory = [...conversationHistory];
7849
+ const validationMessageId = await conversationManager.startMessage(
7850
+ threadId,
7851
+ threadPath,
7852
+ conversationId,
7853
+ validationPrompt,
7854
+ model,
7855
+ void 0,
7856
+ planMode
7857
+ );
7858
+ const validationCaptured = { events: [], fullContent: "" };
7859
+ yield* executeAgent(
7860
+ validationPrompt,
7861
+ context,
7862
+ abortController.signal,
7863
+ updatedHistory,
7864
+ validationCaptured
7865
+ );
7866
+ const validationContent = extractAssistantContent(
7867
+ validationCaptured.events,
7868
+ validationCaptured.fullContent
7869
+ );
7870
+ await conversationManager.completeMessage(
7871
+ validationMessageId,
7872
+ validationContent,
7873
+ validationCaptured.events
7874
+ );
7875
+ conversationHistory.push({
7876
+ userMessage: validationPrompt,
7877
+ assistantResponse: validationContent
7878
+ });
7879
+ decrementBalance().catch((err) => {
7880
+ console.error("[ChatRoute] Failed to decrement balance:", err);
7881
+ });
7882
+ }
7883
+ if (attempt >= MAX_VALIDATION_ATTEMPTS) {
7884
+ const maxAttemptsEvent = {
7885
+ type: "thinking",
7886
+ content: `[Validating] Validation script still failing after ${MAX_VALIDATION_ATTEMPTS} attempts. Stopping.`
7887
+ };
7888
+ processingStateManager.pushEvent(threadId, maxAttemptsEvent);
7889
+ yield maxAttemptsEvent;
7890
+ }
7891
+ }
7575
7892
  } catch (error) {
7576
7893
  const errorMessage = error instanceof Error ? error.message : String(error);
7577
7894
  const errorEvent = {
@@ -7583,17 +7900,17 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
7583
7900
  details: error instanceof Error ? { stack: error.stack } : void 0
7584
7901
  }
7585
7902
  };
7586
- capturedEvents.push(errorEvent);
7903
+ captured.events.push(errorEvent);
7587
7904
  yield errorEvent;
7588
7905
  try {
7589
- const finalContent = extractAssistantContent(capturedEvents, fullContent);
7590
- await conversationManager.completeMessage(messageId, finalContent, capturedEvents);
7906
+ const finalContent = extractAssistantContent(captured.events, captured.fullContent);
7907
+ await conversationManager.completeMessage(messageId, finalContent, captured.events);
7591
7908
  } catch (saveError) {
7592
7909
  console.error("[ChatRoute] Failed to save partial conversation:", saveError);
7593
7910
  }
7594
7911
  } finally {
7595
7912
  processingStateManager.clearProcessing(threadId);
7596
- const noResponse = capturedEvents.some(
7913
+ const noResponse = captured.events.some(
7597
7914
  (e) => e.type === "error" && e.error?.code === "NO_CONTENT_NO_TOOLS"
7598
7915
  );
7599
7916
  if (!noResponse) {
@@ -8862,171 +9179,6 @@ function createConversationRoutes(conversationManager, threadManager) {
8862
9179
  // src/features/metadata/metadata.manager.ts
8863
9180
  init_database();
8864
9181
 
8865
- // src/features/projects/projects.database.ts
8866
- async function insertProject(db, project) {
8867
- try {
8868
- await db.execute(
8869
- `
8870
- INSERT INTO projects (id, name, gitUrl, path, createdAt, openWith, commands, setupScript, runCommand, commitMethod, planPrompt, testPrompt, reviewPrompt)
8871
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8872
- `,
8873
- [
8874
- project.id,
8875
- project.name,
8876
- project.gitUrl,
8877
- project.path,
8878
- project.createdAt.toISOString(),
8879
- project.openWith ?? null,
8880
- project.commands ? JSON.stringify(project.commands) : null,
8881
- project.setupScript ?? null,
8882
- project.runCommand ?? null,
8883
- project.commitMethod ?? null,
8884
- project.planPrompt ?? null,
8885
- project.testPrompt ?? null,
8886
- project.reviewPrompt ?? null
8887
- ]
8888
- );
8889
- } catch (error) {
8890
- console.error("Failed to insert project:", error);
8891
- throw error;
8892
- }
8893
- }
8894
- async function updateProject(db, id, updates) {
8895
- try {
8896
- const fields = [];
8897
- const values = [];
8898
- if (updates.name !== void 0) {
8899
- fields.push("name = ?");
8900
- values.push(updates.name);
8901
- }
8902
- if (updates.gitUrl !== void 0) {
8903
- fields.push("gitUrl = ?");
8904
- values.push(updates.gitUrl);
8905
- }
8906
- if (updates.path !== void 0) {
8907
- fields.push("path = ?");
8908
- values.push(updates.path);
8909
- }
8910
- if (updates.openWith !== void 0) {
8911
- fields.push("openWith = ?");
8912
- values.push(updates.openWith ?? null);
8913
- }
8914
- if (updates.commands !== void 0) {
8915
- fields.push("commands = ?");
8916
- values.push(updates.commands ? JSON.stringify(updates.commands) : null);
8917
- }
8918
- if (updates.setupScript !== void 0) {
8919
- fields.push("setupScript = ?");
8920
- values.push(updates.setupScript ?? null);
8921
- }
8922
- if (updates.runCommand !== void 0) {
8923
- fields.push("runCommand = ?");
8924
- values.push(updates.runCommand ?? null);
8925
- }
8926
- if (updates.commitMethod !== void 0) {
8927
- fields.push("commitMethod = ?");
8928
- values.push(updates.commitMethod ?? null);
8929
- }
8930
- if (updates.planPrompt !== void 0) {
8931
- fields.push("planPrompt = ?");
8932
- values.push(updates.planPrompt ?? null);
8933
- }
8934
- if (updates.testPrompt !== void 0) {
8935
- fields.push("testPrompt = ?");
8936
- values.push(updates.testPrompt ?? null);
8937
- }
8938
- if (updates.reviewPrompt !== void 0) {
8939
- fields.push("reviewPrompt = ?");
8940
- values.push(updates.reviewPrompt ?? null);
8941
- }
8942
- if (fields.length === 0) {
8943
- return;
8944
- }
8945
- values.push(id);
8946
- const sql = `UPDATE projects SET ${fields.join(", ")} WHERE id = ?`;
8947
- await db.execute(sql, values);
8948
- } catch (error) {
8949
- console.error("Failed to update project:", error);
8950
- throw error;
8951
- }
8952
- }
8953
- async function deleteProject(db, id) {
8954
- try {
8955
- await db.execute("PRAGMA foreign_keys = OFF");
8956
- try {
8957
- await db.execute(
8958
- `
8959
- DELETE FROM conversation_history
8960
- WHERE threadId IN (
8961
- SELECT threads.id FROM threads WHERE threads.projectId = ?
8962
- )
8963
- `,
8964
- [id]
8965
- );
8966
- await db.execute(
8967
- `
8968
- DELETE FROM todos
8969
- WHERE threadId IN (
8970
- SELECT threads.id FROM threads WHERE threads.projectId = ?
8971
- )
8972
- `,
8973
- [id]
8974
- );
8975
- await db.execute("DELETE FROM threads WHERE projectId = ?", [id]);
8976
- await db.execute("DELETE FROM project_todos WHERE projectId = ?", [id]);
8977
- await db.execute("DELETE FROM projects WHERE id = ?", [id]);
8978
- } finally {
8979
- await db.execute("PRAGMA foreign_keys = ON");
8980
- }
8981
- } catch (error) {
8982
- console.error("Failed to delete project:", error);
8983
- throw error;
8984
- }
8985
- }
8986
- async function getAllProjects(db) {
8987
- try {
8988
- const result = await db.execute("SELECT * FROM projects ORDER BY createdAt DESC");
8989
- const rows = result.rows;
8990
- const threadResult = await db.execute(
8991
- "SELECT id, projectId FROM threads ORDER BY createdAt DESC"
8992
- );
8993
- const threadRows = threadResult.rows;
8994
- const threadIdsByProject = /* @__PURE__ */ new Map();
8995
- for (const row of threadRows) {
8996
- const ids = threadIdsByProject.get(row.projectId);
8997
- if (ids) {
8998
- ids.push(row.id);
8999
- } else {
9000
- threadIdsByProject.set(row.projectId, [row.id]);
9001
- }
9002
- }
9003
- return rows.map(
9004
- (row) => deserializeProjectWithThreads(row, threadIdsByProject.get(row.id) ?? [])
9005
- );
9006
- } catch (error) {
9007
- console.error("Failed to get all projects:", error);
9008
- throw error;
9009
- }
9010
- }
9011
- function deserializeProjectWithThreads(row, threadIds) {
9012
- return {
9013
- id: row.id,
9014
- name: row.name,
9015
- gitUrl: row.gitUrl,
9016
- path: row.path,
9017
- createdAt: new Date(row.createdAt),
9018
- threads: threadIds,
9019
- openWith: row.openWith ?? void 0,
9020
- commands: row.commands ? JSON.parse(row.commands) : void 0,
9021
- setupScript: row.setupScript ?? void 0,
9022
- runCommand: row.runCommand ?? void 0,
9023
- commitMethod: row.commitMethod && isValidCommitMethod(row.commitMethod) ? row.commitMethod : void 0,
9024
- planPrompt: row.planPrompt ?? void 0,
9025
- testPrompt: row.testPrompt ?? void 0,
9026
- reviewPrompt: row.reviewPrompt ?? void 0
9027
- };
9028
- }
9029
-
9030
9182
  // src/features/threads/threads.database.ts
9031
9183
  async function insertThread(db, thread) {
9032
9184
  try {
@@ -10011,6 +10163,7 @@ async function handleListProjects(c, projectManager, threadManager, db) {
10011
10163
  openWith: project.openWith,
10012
10164
  commands: project.commands ?? [],
10013
10165
  setupScript: project.setupScript,
10166
+ validationScript: project.validationScript,
10014
10167
  runCommand: project.runCommand,
10015
10168
  commitMethod: project.commitMethod,
10016
10169
  planPrompt: project.planPrompt,
@@ -10103,8 +10256,8 @@ function validateProgram(program) {
10103
10256
  return null;
10104
10257
  }
10105
10258
  var OpenWithHandler = class {
10106
- constructor(getProject, metadataManager) {
10107
- this.getProject = getProject;
10259
+ constructor(getProject2, metadataManager) {
10260
+ this.getProject = getProject2;
10108
10261
  this.metadataManager = metadataManager;
10109
10262
  }
10110
10263
  async openWith(projectId, program) {
@@ -10281,6 +10434,7 @@ async function handleUpdateProject(c, projectManager) {
10281
10434
  const {
10282
10435
  program,
10283
10436
  setupScript,
10437
+ validationScript,
10284
10438
  name,
10285
10439
  runCommand,
10286
10440
  commitMethod,
@@ -10311,6 +10465,10 @@ async function handleUpdateProject(c, projectManager) {
10311
10465
  return streamAsyncGenerator(c, projectManager.runCommand(firstThreadId, scriptToSave));
10312
10466
  }
10313
10467
  }
10468
+ if (validationScript !== void 0) {
10469
+ const scriptToSave = validationScript === "" ? null : validationScript;
10470
+ await projectManager.updateValidationScript(projectId, scriptToSave);
10471
+ }
10314
10472
  if (runCommand !== void 0) {
10315
10473
  await projectManager.updateRunCommand(projectId, runCommand);
10316
10474
  }
@@ -11336,7 +11494,7 @@ import { access as access3, stat as stat3, readdir as readdir8 } from "fs/promis
11336
11494
  init_utils();
11337
11495
  import { existsSync as existsSync12 } from "fs";
11338
11496
  import { join as join16 } from "path";
11339
- import { mkdir as mkdir2, readdir as readdir7, stat as stat2, rename, rm as rm2 } from "fs/promises";
11497
+ import { mkdir as mkdir2, readdir as readdir7, stat as stat2, rename, rm as rm2, writeFile as writeFile2 } from "fs/promises";
11340
11498
  import { createInterface as createInterface2 } from "readline";
11341
11499
 
11342
11500
  // src/scaffold-templates.json
@@ -11351,7 +11509,8 @@ var scaffold_templates_default = [
11351
11509
  "#$(project-folder)"
11352
11510
  ],
11353
11511
  variation: "list",
11354
- variationName: "Ionic List"
11512
+ variationName: "Ionic List",
11513
+ agentsMd: "- Use the `inject()` function instead of constructor injection for dependencies.\n- Prefer standalone components; avoid NgModule declarations unless required by a library.\n- Use signals (`signal()`, `computed()`, `effect()`) for reactive state instead of RxJS Subjects where possible.\n- Always run `ng generate` for new components/services rather than creating files manually to ensure correct wiring.\n- Mark change detection as `OnPush` on every component by default to avoid unnecessary re-renders."
11355
11514
  },
11356
11515
  {
11357
11516
  id: "angular-standalone-blank",
@@ -11363,7 +11522,8 @@ var scaffold_templates_default = [
11363
11522
  "#$(project-folder)"
11364
11523
  ],
11365
11524
  variation: "blank",
11366
- variationName: "Ionic Blank"
11525
+ variationName: "Ionic Blank",
11526
+ agentsMd: "- Use the `inject()` function instead of constructor injection for dependencies.\n- Prefer standalone components; avoid NgModule declarations unless required by a library.\n- Use signals (`signal()`, `computed()`, `effect()`) for reactive state instead of RxJS Subjects where possible.\n- Always run `ng generate` for new components/services rather than creating files manually to ensure correct wiring.\n- Mark change detection as `OnPush` on every component by default to avoid unnecessary re-renders."
11367
11527
  },
11368
11528
  {
11369
11529
  id: "angular-standalone-sidemenu",
@@ -11375,7 +11535,8 @@ var scaffold_templates_default = [
11375
11535
  "#$(project-folder)"
11376
11536
  ],
11377
11537
  variation: "sidemenu",
11378
- variationName: "Ionic Side Menu"
11538
+ variationName: "Ionic Side Menu",
11539
+ agentsMd: "- Use the `inject()` function instead of constructor injection for dependencies.\n- Prefer standalone components; avoid NgModule declarations unless required by a library.\n- Use signals (`signal()`, `computed()`, `effect()`) for reactive state instead of RxJS Subjects where possible.\n- Always run `ng generate` for new components/services rather than creating files manually to ensure correct wiring.\n- Mark change detection as `OnPush` on every component by default to avoid unnecessary re-renders."
11379
11540
  },
11380
11541
  {
11381
11542
  id: "angular-standalone-tabs",
@@ -11387,7 +11548,8 @@ var scaffold_templates_default = [
11387
11548
  "#$(project-folder)"
11388
11549
  ],
11389
11550
  variation: "tabs",
11390
- variationName: "Ionic Tabs"
11551
+ variationName: "Ionic Tabs",
11552
+ agentsMd: "- Use the `inject()` function instead of constructor injection for dependencies.\n- Prefer standalone components; avoid NgModule declarations unless required by a library.\n- Use signals (`signal()`, `computed()`, `effect()`) for reactive state instead of RxJS Subjects where possible.\n- Always run `ng generate` for new components/services rather than creating files manually to ensure correct wiring.\n- Mark change detection as `OnPush` on every component by default to avoid unnecessary re-renders."
11391
11553
  },
11392
11554
  {
11393
11555
  id: "react-tabs",
@@ -11399,7 +11561,8 @@ var scaffold_templates_default = [
11399
11561
  "#$(project-folder)"
11400
11562
  ],
11401
11563
  variation: "tabs",
11402
- variationName: "Ionic Tabs"
11564
+ variationName: "Ionic Tabs",
11565
+ agentsMd: "- Extract stateful logic into custom hooks (`use*`) instead of keeping `useState`/`useEffect` directly in components.\n- Avoid adding `useEffect` for data that can be derived synchronously from existing state or props.\n- Wrap expensive child renders in `React.memo` and callbacks in `useCallback` only after profiling, not by default.\n- Use the `key` prop on list items with stable unique IDs \u2014 never array indices when the list can be reordered or filtered.\n- Prefer controlled components over uncontrolled ones so form state is always in sync with application state."
11403
11566
  },
11404
11567
  {
11405
11568
  id: "react-sidemenu",
@@ -11411,7 +11574,8 @@ var scaffold_templates_default = [
11411
11574
  "#$(project-folder)"
11412
11575
  ],
11413
11576
  variation: "sidemenu",
11414
- variationName: "Ionic Side Menu"
11577
+ variationName: "Ionic Side Menu",
11578
+ agentsMd: "- Extract stateful logic into custom hooks (`use*`) instead of keeping `useState`/`useEffect` directly in components.\n- Avoid adding `useEffect` for data that can be derived synchronously from existing state or props.\n- Wrap expensive child renders in `React.memo` and callbacks in `useCallback` only after profiling, not by default.\n- Use the `key` prop on list items with stable unique IDs \u2014 never array indices when the list can be reordered or filtered.\n- Prefer controlled components over uncontrolled ones so form state is always in sync with application state."
11415
11579
  },
11416
11580
  {
11417
11581
  id: "react-list",
@@ -11423,7 +11587,8 @@ var scaffold_templates_default = [
11423
11587
  "#$(project-folder)"
11424
11588
  ],
11425
11589
  variation: "list",
11426
- variationName: "Ionic List"
11590
+ variationName: "Ionic List",
11591
+ agentsMd: "- Extract stateful logic into custom hooks (`use*`) instead of keeping `useState`/`useEffect` directly in components.\n- Avoid adding `useEffect` for data that can be derived synchronously from existing state or props.\n- Wrap expensive child renders in `React.memo` and callbacks in `useCallback` only after profiling, not by default.\n- Use the `key` prop on list items with stable unique IDs \u2014 never array indices when the list can be reordered or filtered.\n- Prefer controlled components over uncontrolled ones so form state is always in sync with application state."
11427
11592
  },
11428
11593
  {
11429
11594
  id: "react-blank",
@@ -11435,7 +11600,8 @@ var scaffold_templates_default = [
11435
11600
  "#$(project-folder)"
11436
11601
  ],
11437
11602
  variation: "blank",
11438
- variationName: "Ionic Blank"
11603
+ variationName: "Ionic Blank",
11604
+ agentsMd: "- Extract stateful logic into custom hooks (`use*`) instead of keeping `useState`/`useEffect` directly in components.\n- Avoid adding `useEffect` for data that can be derived synchronously from existing state or props.\n- Wrap expensive child renders in `React.memo` and callbacks in `useCallback` only after profiling, not by default.\n- Use the `key` prop on list items with stable unique IDs \u2014 never array indices when the list can be reordered or filtered.\n- Prefer controlled components over uncontrolled ones so form state is always in sync with application state."
11439
11605
  },
11440
11606
  {
11441
11607
  id: "vue-list",
@@ -11447,7 +11613,8 @@ var scaffold_templates_default = [
11447
11613
  "#$(project-folder)"
11448
11614
  ],
11449
11615
  variation: "list",
11450
- variationName: "Ionic List"
11616
+ variationName: "Ionic List",
11617
+ agentsMd: "- Use the Composition API (`<script setup>`) for all components; avoid the Options API in new code.\n- Define props with `defineProps<TypedInterface>()` using TypeScript generics rather than runtime validators.\n- Never destructure reactive props directly \u2014 use `toRefs(props)` or access via `props.x` to preserve reactivity.\n- Use `provide`/`inject` with typed injection keys (a `InjectionKey<T>`) to avoid untyped cross-component coupling.\n- Prefix composables with `use` and keep them in a dedicated `composables/` directory, not inside component files."
11451
11618
  },
11452
11619
  {
11453
11620
  id: "vue-blank",
@@ -11459,7 +11626,8 @@ var scaffold_templates_default = [
11459
11626
  "#$(project-folder)"
11460
11627
  ],
11461
11628
  variation: "blank",
11462
- variationName: "Ionic Blank"
11629
+ variationName: "Ionic Blank",
11630
+ agentsMd: "- Use the Composition API (`<script setup>`) for all components; avoid the Options API in new code.\n- Define props with `defineProps<TypedInterface>()` using TypeScript generics rather than runtime validators.\n- Never destructure reactive props directly \u2014 use `toRefs(props)` or access via `props.x` to preserve reactivity.\n- Use `provide`/`inject` with typed injection keys (a `InjectionKey<T>`) to avoid untyped cross-component coupling.\n- Prefix composables with `use` and keep them in a dedicated `composables/` directory, not inside component files."
11463
11631
  },
11464
11632
  {
11465
11633
  id: "vue-sidemenu",
@@ -11471,7 +11639,8 @@ var scaffold_templates_default = [
11471
11639
  "#$(project-folder)"
11472
11640
  ],
11473
11641
  variation: "sidemenu",
11474
- variationName: "Ionic Side Menu"
11642
+ variationName: "Ionic Side Menu",
11643
+ agentsMd: "- Use the Composition API (`<script setup>`) for all components; avoid the Options API in new code.\n- Define props with `defineProps<TypedInterface>()` using TypeScript generics rather than runtime validators.\n- Never destructure reactive props directly \u2014 use `toRefs(props)` or access via `props.x` to preserve reactivity.\n- Use `provide`/`inject` with typed injection keys (a `InjectionKey<T>`) to avoid untyped cross-component coupling.\n- Prefix composables with `use` and keep them in a dedicated `composables/` directory, not inside component files."
11475
11644
  },
11476
11645
  {
11477
11646
  id: "vue-tabs",
@@ -11483,7 +11652,8 @@ var scaffold_templates_default = [
11483
11652
  "#$(project-folder)"
11484
11653
  ],
11485
11654
  variation: "tabs",
11486
- variationName: "Ionic Tabs"
11655
+ variationName: "Ionic Tabs",
11656
+ agentsMd: "- Use the Composition API (`<script setup>`) for all components; avoid the Options API in new code.\n- Define props with `defineProps<TypedInterface>()` using TypeScript generics rather than runtime validators.\n- Never destructure reactive props directly \u2014 use `toRefs(props)` or access via `props.x` to preserve reactivity.\n- Use `provide`/`inject` with typed injection keys (a `InjectionKey<T>`) to avoid untyped cross-component coupling.\n- Prefix composables with `use` and keep them in a dedicated `composables/` directory, not inside component files."
11487
11657
  },
11488
11658
  {
11489
11659
  id: "plugin-starter",
@@ -11493,7 +11663,8 @@ var scaffold_templates_default = [
11493
11663
  url: "https://capacitorjs.com/docs/plugins/creating-plugins",
11494
11664
  handler: "capacitor-plugin",
11495
11665
  variation: "Starter",
11496
- variationName: "Capacitor Plugin Starter"
11666
+ variationName: "Capacitor Plugin Starter",
11667
+ agentsMd: "- Implement every method in the web fallback (`web.ts`) even if it just throws `unimplemented()` \u2014 never leave methods missing.\n- Declare all plugin events in the `addListener` overloads with explicit typed payloads, not generic `any` objects.\n- Use `Capacitor.isNativePlatform()` guards before calling native-only APIs rather than checking `platform === 'ios'`.\n- Always call `notifyListeners` with a consistent event-name constant shared between native and web implementations.\n- Run `npx cap sync` after any change to native code or package exports to keep the native bridge in sync."
11497
11668
  },
11498
11669
  {
11499
11670
  id: "custom-angular-starter",
@@ -11506,7 +11677,8 @@ var scaffold_templates_default = [
11506
11677
  "#$(project-folder)"
11507
11678
  ],
11508
11679
  variation: "Starter",
11509
- variationName: "Starter"
11680
+ variationName: "Starter",
11681
+ agentsMd: "- Use the `inject()` function instead of constructor injection for dependencies.\n- Prefer standalone components; avoid NgModule declarations unless required by a library.\n- Use signals (`signal()`, `computed()`, `effect()`) for reactive state instead of RxJS Subjects where possible.\n- Always run `ng generate` for new components/services rather than creating files manually to ensure correct wiring.\n- Mark change detection as `OnPush` on every component by default to avoid unnecessary re-renders."
11510
11682
  },
11511
11683
  {
11512
11684
  id: "custom-svelte-minimal",
@@ -11519,7 +11691,8 @@ var scaffold_templates_default = [
11519
11691
  "#$(project-folder)"
11520
11692
  ],
11521
11693
  variation: "Minimal",
11522
- variationName: "Minimal"
11694
+ variationName: "Minimal",
11695
+ agentsMd: "- Always reassign arrays/objects to trigger reactivity; never mutate in place (e.g. `arr = [...arr, item]` not `arr.push(item)`).\n- Use Svelte stores (`writable`, `readable`, `derived`) for cross-component reactive state rather than prop drilling.\n- Place reusable logic in plain `.ts` files rather than creating single-instance components just to share behaviour.\n- Scope CSS inside `<style>` blocks \u2014 avoid global styles unless intentionally applying app-wide resets.\n- Use `{#key expr}` blocks to force re-mounting a component when identity changes, rather than toggling with `{#if}`."
11523
11696
  },
11524
11697
  {
11525
11698
  id: "custom-svelte-demo",
@@ -11532,7 +11705,8 @@ var scaffold_templates_default = [
11532
11705
  "#$(project-folder)"
11533
11706
  ],
11534
11707
  variation: "Demo",
11535
- variationName: "Demo"
11708
+ variationName: "Demo",
11709
+ agentsMd: "- Always reassign arrays/objects to trigger reactivity; never mutate in place (e.g. `arr = [...arr, item]` not `arr.push(item)`).\n- Use Svelte stores (`writable`, `readable`, `derived`) for cross-component reactive state rather than prop drilling.\n- Place reusable logic in plain `.ts` files rather than creating single-instance components just to share behaviour.\n- Scope CSS inside `<style>` blocks \u2014 avoid global styles unless intentionally applying app-wide resets.\n- Use `{#key expr}` blocks to force re-mounting a component when identity changes, rather than toggling with `{#if}`."
11536
11710
  },
11537
11711
  {
11538
11712
  id: "custom-svelte-library",
@@ -11545,7 +11719,8 @@ var scaffold_templates_default = [
11545
11719
  "#$(project-folder)"
11546
11720
  ],
11547
11721
  variation: "Library",
11548
- variationName: "Library"
11722
+ variationName: "Library",
11723
+ agentsMd: "- Always reassign arrays/objects to trigger reactivity; never mutate in place (e.g. `arr = [...arr, item]` not `arr.push(item)`).\n- Use Svelte stores (`writable`, `readable`, `derived`) for cross-component reactive state rather than prop drilling.\n- Place reusable logic in plain `.ts` files rather than creating single-instance components just to share behaviour.\n- Scope CSS inside `<style>` blocks \u2014 avoid global styles unless intentionally applying app-wide resets.\n- Use `{#key expr}` blocks to force re-mounting a component when identity changes, rather than toggling with `{#if}`."
11549
11724
  },
11550
11725
  {
11551
11726
  id: "custom-ionic-svelte-starter",
@@ -11555,7 +11730,8 @@ var scaffold_templates_default = [
11555
11730
  url: "https://github.com/Tommertom/svelte-ionic-app",
11556
11731
  commands: ["npm create ionic-svelte-app@latest $(project-name)", "#$(project-folder)"],
11557
11732
  variation: "Starter",
11558
- variationName: "Starter"
11733
+ variationName: "Starter",
11734
+ agentsMd: "- Always reassign arrays/objects to trigger reactivity; never mutate in place (e.g. `arr = [...arr, item]` not `arr.push(item)`).\n- Use Svelte stores (`writable`, `readable`, `derived`) for cross-component reactive state rather than prop drilling.\n- Place reusable logic in plain `.ts` files rather than creating single-instance components just to share behaviour.\n- Scope CSS inside `<style>` blocks \u2014 avoid global styles unless intentionally applying app-wide resets.\n- Use `{#key expr}` blocks to force re-mounting a component when identity changes, rather than toggling with `{#if}`."
11559
11735
  },
11560
11736
  {
11561
11737
  id: "tanstack-start-basic",
@@ -11568,7 +11744,8 @@ var scaffold_templates_default = [
11568
11744
  "#$(project-folder)"
11569
11745
  ],
11570
11746
  variation: "Basic",
11571
- variationName: "Basic"
11747
+ variationName: "Basic",
11748
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11572
11749
  },
11573
11750
  {
11574
11751
  id: "tanstack-start-auth",
@@ -11581,7 +11758,8 @@ var scaffold_templates_default = [
11581
11758
  "#$(project-folder)"
11582
11759
  ],
11583
11760
  variation: "Auth",
11584
- variationName: "Auth"
11761
+ variationName: "Auth",
11762
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11585
11763
  },
11586
11764
  {
11587
11765
  id: "tanstack-start-counter",
@@ -11594,7 +11772,8 @@ var scaffold_templates_default = [
11594
11772
  "#$(project-folder)"
11595
11773
  ],
11596
11774
  variation: "Counter",
11597
- variationName: "Counter"
11775
+ variationName: "Counter",
11776
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11598
11777
  },
11599
11778
  {
11600
11779
  id: "tanstack-start-react-query",
@@ -11607,7 +11786,8 @@ var scaffold_templates_default = [
11607
11786
  "#$(project-folder)"
11608
11787
  ],
11609
11788
  variation: "React Query",
11610
- variationName: "React Query"
11789
+ variationName: "React Query",
11790
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11611
11791
  },
11612
11792
  {
11613
11793
  id: "tanstack-start-clerk-auth",
@@ -11620,7 +11800,8 @@ var scaffold_templates_default = [
11620
11800
  "#$(project-folder)"
11621
11801
  ],
11622
11802
  variation: "Clerk Auth",
11623
- variationName: "Clerk Auth"
11803
+ variationName: "Clerk Auth",
11804
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11624
11805
  },
11625
11806
  {
11626
11807
  id: "tanstack-start-supabase",
@@ -11633,7 +11814,8 @@ var scaffold_templates_default = [
11633
11814
  "#$(project-folder)"
11634
11815
  ],
11635
11816
  variation: "Supabase",
11636
- variationName: "Supabase"
11817
+ variationName: "Supabase",
11818
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11637
11819
  },
11638
11820
  {
11639
11821
  id: "tanstack-start-trellaux",
@@ -11646,7 +11828,8 @@ var scaffold_templates_default = [
11646
11828
  "#$(project-folder)"
11647
11829
  ],
11648
11830
  variation: "Trellaux",
11649
- variationName: "Trellaux"
11831
+ variationName: "Trellaux",
11832
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11650
11833
  },
11651
11834
  {
11652
11835
  id: "tanstack-start-material-ui",
@@ -11659,7 +11842,8 @@ var scaffold_templates_default = [
11659
11842
  "#$(project-folder)"
11660
11843
  ],
11661
11844
  variation: "Material UI",
11662
- variationName: "Material UI"
11845
+ variationName: "Material UI",
11846
+ agentsMd: "- Define routes using the file-based routing convention (`routes/` directory) rather than programmatic route config where possible.\n- Use `createServerFn` for all server-side data fetching and mutations \u2014 never call APIs directly from client components.\n- Throw `redirect()` and `notFound()` inside loaders rather than returning them as data values.\n- Use `routeOptions.staleTime` to control cache lifetimes per route instead of relying on global defaults.\n- Validate search params with a schema (e.g. Zod via `validateSearch`) rather than trusting raw URL string values."
11663
11847
  },
11664
11848
  {
11665
11849
  id: "vite-vue-vue",
@@ -11669,7 +11853,8 @@ var scaffold_templates_default = [
11669
11853
  url: "https://vite.dev/guide/",
11670
11854
  commands: ["npm create vite@latest $(project-name) -- --template vue", "#$(project-folder)"],
11671
11855
  variation: "Vue",
11672
- variationName: "Starter"
11856
+ variationName: "Starter",
11857
+ agentsMd: "- Use the Composition API (`<script setup>`) for all components; avoid the Options API in new code.\n- Define props with `defineProps<TypedInterface>()` using TypeScript generics rather than runtime validators.\n- Never destructure reactive props directly \u2014 use `toRefs(props)` or access via `props.x` to preserve reactivity.\n- Use `provide`/`inject` with typed injection keys (a `InjectionKey<T>`) to avoid untyped cross-component coupling.\n- Prefix composables with `use` and keep them in a dedicated `composables/` directory, not inside component files."
11673
11858
  },
11674
11859
  {
11675
11860
  id: "vite-react-react",
@@ -11682,7 +11867,8 @@ var scaffold_templates_default = [
11682
11867
  "#$(project-folder)"
11683
11868
  ],
11684
11869
  variation: "React",
11685
- variationName: "Starter"
11870
+ variationName: "Starter",
11871
+ agentsMd: "- Extract stateful logic into custom hooks (`use*`) instead of keeping `useState`/`useEffect` directly in components.\n- Avoid adding `useEffect` for data that can be derived synchronously from existing state or props.\n- Wrap expensive child renders in `React.memo` and callbacks in `useCallback` only after profiling, not by default.\n- Use the `key` prop on list items with stable unique IDs \u2014 never array indices when the list can be reordered or filtered.\n- Prefer controlled components over uncontrolled ones so form state is always in sync with application state."
11686
11872
  },
11687
11873
  {
11688
11874
  id: "vite-preact-preact",
@@ -11695,7 +11881,8 @@ var scaffold_templates_default = [
11695
11881
  "#$(project-folder)"
11696
11882
  ],
11697
11883
  variation: "Preact",
11698
- variationName: "Starter"
11884
+ variationName: "Starter",
11885
+ agentsMd: "- Use Preact Signals (`@preact/signals`) for shared reactive state instead of `useState` + prop drilling.\n- Import from `preact/hooks` and `preact/jsx-runtime` explicitly \u2014 do not assume React aliases are configured.\n- Avoid mixing the compat layer (`preact/compat`) with native Preact APIs in the same file.\n- Keep components as small pure functions; Preact's VDOM diffing is fast but unnecessary re-renders still add up.\n- Test with `@preact/test-utils` rather than React Testing Library to avoid compat-layer edge cases."
11699
11886
  },
11700
11887
  {
11701
11888
  id: "vite-lit-lit",
@@ -11708,7 +11895,8 @@ var scaffold_templates_default = [
11708
11895
  "#$(project-folder)"
11709
11896
  ],
11710
11897
  variation: "Lit",
11711
- variationName: "Lit"
11898
+ variationName: "Lit",
11899
+ agentsMd: "- Always declare reactive properties with `@property` or `@state` decorators \u2014 never store component state in plain class fields.\n- Implement `disconnectedCallback` to clean up event listeners and timers added in `connectedCallback`.\n- Use `unsafeHTML` / `unsafeCSS` only when strictly necessary and sanitize any user-supplied content first.\n- Prefer CSS custom properties for theming so consumers can restyle elements without shadow-DOM piercing hacks.\n- Use `@query` / `@queryAll` decorators to access shadow DOM nodes rather than calling `this.shadowRoot.querySelector` inline."
11712
11900
  },
11713
11901
  {
11714
11902
  id: "vite-svelte-svelte",
@@ -11721,7 +11909,8 @@ var scaffold_templates_default = [
11721
11909
  "#$(project-folder)"
11722
11910
  ],
11723
11911
  variation: "Svelte",
11724
- variationName: "Svelte"
11912
+ variationName: "Svelte",
11913
+ agentsMd: "- Always reassign arrays/objects to trigger reactivity; never mutate in place (e.g. `arr = [...arr, item]` not `arr.push(item)`).\n- Use Svelte stores (`writable`, `readable`, `derived`) for cross-component reactive state rather than prop drilling.\n- Place reusable logic in plain `.ts` files rather than creating single-instance components just to share behaviour.\n- Scope CSS inside `<style>` blocks \u2014 avoid global styles unless intentionally applying app-wide resets.\n- Use `{#key expr}` blocks to force re-mounting a component when identity changes, rather than toggling with `{#if}`."
11725
11914
  },
11726
11915
  {
11727
11916
  id: "vite-solid-typescript",
@@ -11734,7 +11923,8 @@ var scaffold_templates_default = [
11734
11923
  "#$(project-folder)"
11735
11924
  ],
11736
11925
  variation: "Typescript",
11737
- variationName: "Typescript"
11926
+ variationName: "Typescript",
11927
+ agentsMd: "- Remember that components run only once \u2014 do not write logic that assumes repeated execution of the component body.\n- Access signal values inside JSX or effects (not in component scope) so tracking is active when the value is read.\n- Use `splitProps` to separate your own props from those forwarded to a DOM element; never spread all props blindly.\n- Wrap async data fetching in `createResource` rather than mixing `createSignal` + `createEffect` for loading state.\n- Use `<For>`, `<Show>`, and `<Switch>` primitives instead of `.map()` or ternary expressions for reactive control flow."
11738
11928
  },
11739
11929
  {
11740
11930
  id: "vite-solid-javascript",
@@ -11744,7 +11934,8 @@ var scaffold_templates_default = [
11744
11934
  url: "https://vite.dev/guide/#trying-vite-online",
11745
11935
  commands: ["npx degit solidjs/templates/js $(project-name)", "#$(project-folder)"],
11746
11936
  variation: "Javascript",
11747
- variationName: "Javascript"
11937
+ variationName: "Javascript",
11938
+ agentsMd: "- Remember that components run only once \u2014 do not write logic that assumes repeated execution of the component body.\n- Access signal values inside JSX or effects (not in component scope) so tracking is active when the value is read.\n- Use `splitProps` to separate your own props from those forwarded to a DOM element; never spread all props blindly.\n- Wrap async data fetching in `createResource` rather than mixing `createSignal` + `createEffect` for loading state.\n- Use `<For>`, `<Show>`, and `<Switch>` primitives instead of `.map()` or ternary expressions for reactive control flow."
11748
11939
  },
11749
11940
  {
11750
11941
  id: "vite-web-typescript",
@@ -11757,7 +11948,8 @@ var scaffold_templates_default = [
11757
11948
  "#$(project-folder)"
11758
11949
  ],
11759
11950
  variation: "Typescript",
11760
- variationName: "Typescript"
11951
+ variationName: "Typescript",
11952
+ agentsMd: "- Use ES modules (`import`/`export`) and rely on Vite's native module resolution instead of bundling everything into one file.\n- Target modern browser APIs; avoid polyfilling features that are natively supported in the browsers you ship to.\n- Keep DOM query selectors (`getElementById`, `querySelector`) at the top of the module, not inside event handlers.\n- Use CSS custom properties for theme tokens rather than hard-coding colour or spacing values in JavaScript.\n- Prefer `fetch` + `async/await` over XMLHttpRequest or callback-style patterns for all network calls."
11761
11953
  },
11762
11954
  {
11763
11955
  id: "vite-web-javascript",
@@ -11770,7 +11962,8 @@ var scaffold_templates_default = [
11770
11962
  "#$(project-folder)"
11771
11963
  ],
11772
11964
  variation: "Javascript",
11773
- variationName: "Javascript"
11965
+ variationName: "Javascript",
11966
+ agentsMd: "- Use ES modules (`import`/`export`) and rely on Vite's native module resolution instead of bundling everything into one file.\n- Target modern browser APIs; avoid polyfilling features that are natively supported in the browsers you ship to.\n- Keep DOM query selectors (`getElementById`, `querySelector`) at the top of the module, not inside event handlers.\n- Use CSS custom properties for theme tokens rather than hard-coding colour or spacing values in JavaScript.\n- Prefer `fetch` + `async/await` over XMLHttpRequest or callback-style patterns for all network calls."
11774
11967
  },
11775
11968
  {
11776
11969
  id: "vite-qwik-qwik",
@@ -11783,7 +11976,8 @@ var scaffold_templates_default = [
11783
11976
  "#$(project-folder)"
11784
11977
  ],
11785
11978
  variation: "Qwik",
11786
- variationName: "Qwik"
11979
+ variationName: "Qwik",
11980
+ agentsMd: "- Mark event handlers and async operations with `$` (e.g. `onClick$`) to define lazy-loadable boundaries \u2014 never inline complex logic.\n- Use `useStore` for mutable component state instead of `useSignal` when the object has multiple related fields.\n- Keep serializable data only in `useStore`/`useSignal` \u2014 functions, class instances, and closures cannot be serialized across resumability boundaries.\n- Run Qwik Insights or the bundle analyser to verify lazy-loading boundaries are correct before shipping.\n- Avoid importing large third-party libraries at the top level; wrap them in a `$` handler to keep initial JS to zero."
11787
11981
  },
11788
11982
  {
11789
11983
  id: "nuxt-nuxt",
@@ -11796,7 +11990,8 @@ var scaffold_templates_default = [
11796
11990
  "#$(project-folder)"
11797
11991
  ],
11798
11992
  variation: "Nuxt",
11799
- variationName: "Nuxt"
11993
+ variationName: "Nuxt",
11994
+ agentsMd: "- Use `useFetch` or `useAsyncData` for all data loading \u2014 never call `fetch` directly inside `<script setup>` as it breaks SSR hydration.\n- Put server-only code in the `server/` directory (API routes, middleware); it is never sent to the client.\n- Use `useState` for shared SSR-safe state across components instead of plain `ref` at module scope.\n- Register Nuxt modules in `nuxt.config.ts` rather than importing their Vue plugins manually.\n- Type server API responses by importing the inferred return type from the route file rather than duplicating the shape."
11800
11995
  },
11801
11996
  {
11802
11997
  id: "astro-astro",
@@ -11809,7 +12004,8 @@ var scaffold_templates_default = [
11809
12004
  "#$(project-folder)"
11810
12005
  ],
11811
12006
  variation: "Astro",
11812
- variationName: "Astro"
12007
+ variationName: "Astro",
12008
+ agentsMd: "- Default to zero client-side JavaScript; only add `client:load` / `client:visible` directives to components that genuinely need interactivity.\n- Use Astro's content collections (`src/content/`) with typed schemas for any structured data rather than loose markdown files.\n- Fetch data at build time in frontmatter (`---` blocks) rather than at runtime unless the page is explicitly server-rendered.\n- Keep shared layout logic in `src/layouts/` and avoid duplicating `<head>` or navigation markup across pages.\n- Use `Astro.locals` and middleware (`src/middleware.ts`) for request-scoped server data instead of passing it through props."
11813
12009
  },
11814
12010
  {
11815
12011
  id: "waku-waku",
@@ -11822,7 +12018,8 @@ var scaffold_templates_default = [
11822
12018
  "#$(project-folder)"
11823
12019
  ],
11824
12020
  variation: "Waku",
11825
- variationName: "Waku"
12021
+ variationName: "Waku",
12022
+ agentsMd: "- Mark components as server components by default; only add `'use client'` when the component needs browser APIs or event handlers.\n- Never pass non-serializable values (functions, class instances) across the server/client boundary as props.\n- Fetch data directly in server components using `async/await` \u2014 avoid additional data-fetching libraries for server-side data.\n- Use Waku's file-based routing (`pages/` directory) instead of configuring routes programmatically.\n- Test server component output by inspecting the RSC payload, not just the rendered HTML, to catch serialization issues early."
11826
12023
  },
11827
12024
  {
11828
12025
  id: "nextjs-nextjs",
@@ -11835,7 +12032,8 @@ var scaffold_templates_default = [
11835
12032
  "#$(project-folder)"
11836
12033
  ],
11837
12034
  variation: "Next.js",
11838
- variationName: "Next.js"
12035
+ variationName: "Next.js",
12036
+ agentsMd: "- Default every component to a Server Component; only add `'use client'` at the leaf level where interactivity is needed.\n- Use Server Actions (functions marked `'use server'`) for form submissions and mutations instead of separate API routes.\n- Avoid `useEffect` for data fetching \u2014 fetch directly in async Server Components or use `cache()` to deduplicate requests.\n- Set explicit `revalidate` or `dynamic` export values in each route segment so caching behaviour is intentional, not accidental.\n- Use `next/image` for all images and supply explicit `width`/`height` or `fill` to prevent layout shift."
11839
12037
  },
11840
12038
  {
11841
12039
  id: "hydrogen-tailwind",
@@ -11848,7 +12046,8 @@ var scaffold_templates_default = [
11848
12046
  "#$(project-folder)"
11849
12047
  ],
11850
12048
  variation: "Tailwind",
11851
- variationName: "Tailwind"
12049
+ variationName: "Tailwind",
12050
+ agentsMd: "- Use Hydrogen's `createStorefrontClient` and never call Shopify's Storefront API directly from client components.\n- Leverage Remix loaders and actions for all data fetching and cart mutations \u2014 avoid `useEffect`-driven fetches.\n- Cache Storefront API responses using the `CacheShort()` / `CacheLong()` strategies from `@shopify/hydrogen` rather than leaving caching implicit.\n- Use `<Money>`, `<Image>`, and `<AddToCartButton>` Hydrogen components instead of reimplementing currency formatting or Shopify CDN URLs.\n- Store session data (cart ID, customer token) via `session.set` in the Remix session rather than in client-side state or localStorage."
11852
12051
  },
11853
12052
  {
11854
12053
  id: "hydrogen-vanilla",
@@ -11861,7 +12060,8 @@ var scaffold_templates_default = [
11861
12060
  "#$(project-folder)"
11862
12061
  ],
11863
12062
  variation: "Vanilla",
11864
- variationName: "Vanilla"
12063
+ variationName: "Vanilla",
12064
+ agentsMd: "- Use Hydrogen's `createStorefrontClient` and never call Shopify's Storefront API directly from client components.\n- Leverage Remix loaders and actions for all data fetching and cart mutations \u2014 avoid `useEffect`-driven fetches.\n- Cache Storefront API responses using the `CacheShort()` / `CacheLong()` strategies from `@shopify/hydrogen` rather than leaving caching implicit.\n- Use `<Money>`, `<Image>`, and `<AddToCartButton>` Hydrogen components instead of reimplementing currency formatting or Shopify CDN URLs.\n- Store session data (cart ID, customer token) via `session.set` in the Remix session rather than in client-side state or localStorage."
11865
12065
  },
11866
12066
  {
11867
12067
  id: "hydrogen-css-modules",
@@ -11874,7 +12074,8 @@ var scaffold_templates_default = [
11874
12074
  "#$(project-folder)"
11875
12075
  ],
11876
12076
  variation: "CSS Modules",
11877
- variationName: "CSS Modules"
12077
+ variationName: "CSS Modules",
12078
+ agentsMd: "- Use Hydrogen's `createStorefrontClient` and never call Shopify's Storefront API directly from client components.\n- Leverage Remix loaders and actions for all data fetching and cart mutations \u2014 avoid `useEffect`-driven fetches.\n- Cache Storefront API responses using the `CacheShort()` / `CacheLong()` strategies from `@shopify/hydrogen` rather than leaving caching implicit.\n- Use `<Money>`, `<Image>`, and `<AddToCartButton>` Hydrogen components instead of reimplementing currency formatting or Shopify CDN URLs.\n- Store session data (cart ID, customer token) via `session.set` in the Remix session rather than in client-side state or localStorage."
11878
12079
  },
11879
12080
  {
11880
12081
  id: "hydrogen-post-css",
@@ -11887,7 +12088,120 @@ var scaffold_templates_default = [
11887
12088
  "#$(project-folder)"
11888
12089
  ],
11889
12090
  variation: "Post CSS",
11890
- variationName: "Post CSS"
12091
+ variationName: "Post CSS",
12092
+ agentsMd: "- Use Hydrogen's `createStorefrontClient` and never call Shopify's Storefront API directly from client components.\n- Leverage Remix loaders and actions for all data fetching and cart mutations \u2014 avoid `useEffect`-driven fetches.\n- Cache Storefront API responses using the `CacheShort()` / `CacheLong()` strategies from `@shopify/hydrogen` rather than leaving caching implicit.\n- Use `<Money>`, `<Image>`, and `<AddToCartButton>` Hydrogen components instead of reimplementing currency formatting or Shopify CDN URLs.\n- Store session data (cart ID, customer token) via `session.set` in the Remix session rather than in client-side state or localStorage."
12093
+ },
12094
+ {
12095
+ id: "vite-plus-vanilla-ts",
12096
+ type: "vite-web",
12097
+ typeName: "Web",
12098
+ description: "A starter Web project with Vite Plus (vanilla-ts)",
12099
+ url: "https://viteplus.dev/guide/create",
12100
+ commands: [
12101
+ "vp create vite --directory $(project-name) --no-interactive -- --template vanilla-ts",
12102
+ "#$(project-folder)"
12103
+ ],
12104
+ variation: "vite-plus",
12105
+ variationName: "Vite Plus",
12106
+ agentsMd: "- Use ES modules (`import`/`export`) and rely on Vite's native module resolution instead of bundling everything into one file.\n- Target modern browser APIs; avoid polyfilling features that are natively supported in the browsers you ship to.\n- Keep DOM query selectors (`getElementById`, `querySelector`) at the top of the module, not inside event handlers.\n- Use CSS custom properties for theme tokens rather than hard-coding colour or spacing values in JavaScript.\n- Prefer `fetch` + `async/await` over XMLHttpRequest or callback-style patterns for all network calls."
12107
+ },
12108
+ {
12109
+ id: "vite-plus-react-ts",
12110
+ type: "vite-react",
12111
+ typeName: "React",
12112
+ description: "A starter React project with Vite Plus (react-ts)",
12113
+ url: "https://viteplus.dev/guide/create",
12114
+ commands: [
12115
+ "vp create vite --directory $(project-name) --no-interactive -- --template react-ts",
12116
+ "#$(project-folder)"
12117
+ ],
12118
+ variation: "vite-plus",
12119
+ variationName: "Vite Plus",
12120
+ agentsMd: "- Extract stateful logic into custom hooks (`use*`) instead of keeping `useState`/`useEffect` directly in components.\n- Avoid adding `useEffect` for data that can be derived synchronously from existing state or props.\n- Wrap expensive child renders in `React.memo` and callbacks in `useCallback` only after profiling, not by default.\n- Use the `key` prop on list items with stable unique IDs \u2014 never array indices when the list can be reordered or filtered.\n- Prefer controlled components over uncontrolled ones so form state is always in sync with application state."
12121
+ },
12122
+ {
12123
+ id: "vite-plus-vue-ts",
12124
+ type: "vite-vue",
12125
+ typeName: "Vue",
12126
+ description: "A starter Vue project with Vite Plus (vue-ts)",
12127
+ url: "https://viteplus.dev/guide/create",
12128
+ commands: [
12129
+ "vp create vite --directory $(project-name) --no-interactive -- --template vue-ts",
12130
+ "#$(project-folder)"
12131
+ ],
12132
+ variation: "vite-plus",
12133
+ variationName: "Vite Plus",
12134
+ agentsMd: "- Use the Composition API (`<script setup>`) for all components; avoid the Options API in new code.\n- Define props with `defineProps<TypedInterface>()` using TypeScript generics rather than runtime validators.\n- Never destructure reactive props directly \u2014 use `toRefs(props)` or access via `props.x` to preserve reactivity.\n- Use `provide`/`inject` with typed injection keys (a `InjectionKey<T>`) to avoid untyped cross-component coupling.\n- Prefix composables with `use` and keep them in a dedicated `composables/` directory, not inside component files."
12135
+ },
12136
+ {
12137
+ id: "vite-plus-svelte-ts",
12138
+ type: "vite-svelte",
12139
+ typeName: "Svelte",
12140
+ description: "A starter Svelte project with Vite Plus (svelte-ts)",
12141
+ url: "https://viteplus.dev/guide/create",
12142
+ commands: [
12143
+ "vp create vite --directory $(project-name) --no-interactive -- --template svelte-ts",
12144
+ "#$(project-folder)"
12145
+ ],
12146
+ variation: "vite-plus",
12147
+ variationName: "Vite Plus",
12148
+ agentsMd: "- Always reassign arrays/objects to trigger reactivity; never mutate in place (e.g. `arr = [...arr, item]` not `arr.push(item)`).\n- Use Svelte stores (`writable`, `readable`, `derived`) for cross-component reactive state rather than prop drilling.\n- Place reusable logic in plain `.ts` files rather than creating single-instance components just to share behaviour.\n- Scope CSS inside `<style>` blocks \u2014 avoid global styles unless intentionally applying app-wide resets.\n- Use `{#key expr}` blocks to force re-mounting a component when identity changes, rather than toggling with `{#if}`."
12149
+ },
12150
+ {
12151
+ id: "vite-plus-solid-ts",
12152
+ type: "vite-solid",
12153
+ typeName: "Solid",
12154
+ description: "A starter Solid project with Vite Plus (solid-ts)",
12155
+ url: "https://viteplus.dev/guide/create",
12156
+ commands: [
12157
+ "vp create vite --directory $(project-name) --no-interactive -- --template solid-ts",
12158
+ "#$(project-folder)"
12159
+ ],
12160
+ variation: "vite-plus",
12161
+ variationName: "Vite Plus",
12162
+ agentsMd: "- Remember that components run only once \u2014 do not write logic that assumes repeated execution of the component body.\n- Access signal values inside JSX or effects (not in component scope) so tracking is active when the value is read.\n- Use `splitProps` to separate your own props from those forwarded to a DOM element; never spread all props blindly.\n- Wrap async data fetching in `createResource` rather than mixing `createSignal` + `createEffect` for loading state.\n- Use `<For>`, `<Show>`, and `<Switch>` primitives instead of `.map()` or ternary expressions for reactive control flow."
12163
+ },
12164
+ {
12165
+ id: "vite-plus-preact-ts",
12166
+ type: "vite-preact",
12167
+ typeName: "Preact",
12168
+ description: "A starter Preact project with Vite Plus (preact-ts)",
12169
+ url: "https://viteplus.dev/guide/create",
12170
+ commands: [
12171
+ "vp create vite --directory $(project-name) --no-interactive -- --template preact-ts",
12172
+ "#$(project-folder)"
12173
+ ],
12174
+ variation: "vite-plus",
12175
+ variationName: "Vite Plus",
12176
+ agentsMd: "- Use Preact Signals (`@preact/signals`) for shared reactive state instead of `useState` + prop drilling.\n- Import from `preact/hooks` and `preact/jsx-runtime` explicitly \u2014 do not assume React aliases are configured.\n- Avoid mixing the compat layer (`preact/compat`) with native Preact APIs in the same file.\n- Keep components as small pure functions; Preact's VDOM diffing is fast but unnecessary re-renders still add up.\n- Test with `@preact/test-utils` rather than React Testing Library to avoid compat-layer edge cases."
12177
+ },
12178
+ {
12179
+ id: "vite-plus-lit-ts",
12180
+ type: "vite-lit",
12181
+ typeName: "Lit",
12182
+ description: "A starter Lit project with Vite Plus (lit-ts)",
12183
+ url: "https://viteplus.dev/guide/create",
12184
+ commands: [
12185
+ "vp create vite --directory $(project-name) --no-interactive -- --template lit-ts",
12186
+ "#$(project-folder)"
12187
+ ],
12188
+ variation: "vite-plus",
12189
+ variationName: "Vite Plus",
12190
+ agentsMd: "- Always declare reactive properties with `@property` or `@state` decorators \u2014 never store component state in plain class fields.\n- Implement `disconnectedCallback` to clean up event listeners and timers added in `connectedCallback`.\n- Use `unsafeHTML` / `unsafeCSS` only when strictly necessary and sanitize any user-supplied content first.\n- Prefer CSS custom properties for theming so consumers can restyle elements without shadow-DOM piercing hacks.\n- Use `@query` / `@queryAll` decorators to access shadow DOM nodes rather than calling `this.shadowRoot.querySelector` inline."
12191
+ },
12192
+ {
12193
+ id: "vite-plus-qwik-ts",
12194
+ type: "vite-qwik",
12195
+ typeName: "Qwik",
12196
+ description: "A starter Qwik project with Vite Plus (qwik-ts)",
12197
+ url: "https://viteplus.dev/guide/create",
12198
+ commands: [
12199
+ "vp create vite --directory $(project-name) --no-interactive -- --template qwik-ts",
12200
+ "#$(project-folder)"
12201
+ ],
12202
+ variation: "vite-plus",
12203
+ variationName: "Vite Plus",
12204
+ agentsMd: "- Mark event handlers and async operations with `$` (e.g. `onClick$`) to define lazy-loadable boundaries \u2014 never inline complex logic.\n- Use `useStore` for mutable component state instead of `useSignal` when the object has multiple related fields.\n- Keep serializable data only in `useStore`/`useSignal` \u2014 functions, class instances, and closures cannot be serialized across resumability boundaries.\n- Run Qwik Insights or the bundle analyser to verify lazy-loading boundaries are correct before shipping.\n- Avoid importing large third-party libraries at the top level; wrap them in a `$` handler to keep initial JS to zero."
11891
12205
  }
11892
12206
  ];
11893
12207
 
@@ -11899,6 +12213,12 @@ function registerScaffoldHandler(handlerName, callback) {
11899
12213
  function loadCatalog() {
11900
12214
  return scaffold_templates_default;
11901
12215
  }
12216
+ async function writeAgentsMd(projectPath, agentsMd) {
12217
+ if (!agentsMd) return;
12218
+ const agentsPath = join16(projectPath, "AGENTS.md");
12219
+ if (existsSync12(agentsPath)) return;
12220
+ await writeFile2(agentsPath, agentsMd, "utf-8");
12221
+ }
11902
12222
  function getProjectName(name) {
11903
12223
  return name.toLowerCase().replace(/\s/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-(\d)/g, "-a$1");
11904
12224
  }
@@ -11933,7 +12253,10 @@ function substitutePlaceholders(command, options, template) {
11933
12253
  function applyPackageManagerSubstitution(command, pm) {
11934
12254
  const trimmed = command.trim();
11935
12255
  if (trimmed.startsWith("npm create ")) {
11936
- const rest = trimmed.slice("npm create ".length);
12256
+ let rest = trimmed.slice("npm create ".length);
12257
+ if (pm !== "npm") {
12258
+ rest = rest.replace(" -- ", " ");
12259
+ }
11937
12260
  switch (pm) {
11938
12261
  case "bun":
11939
12262
  return `bun create ${rest}`;
@@ -12115,6 +12438,8 @@ async function* createScaffoldedProjectStreaming(options) {
12115
12438
  continue;
12116
12439
  }
12117
12440
  const cmd = raw.startsWith("npm ") || raw.startsWith("npx ") ? applyPackageManagerSubstitution(raw, options.packageManager) : raw;
12441
+ yield { type: "progress", message: `
12442
+ > ${cmd}` };
12118
12443
  for await (const event of runCommandStreaming(
12119
12444
  cwd === options.parentDir ? projectPath : cwd,
12120
12445
  cmd
@@ -12167,6 +12492,8 @@ async function* createScaffoldedProjectStreaming(options) {
12167
12492
  const installCwd = scaffoldOptions.projectPath;
12168
12493
  if (existsSync12(installCwd)) {
12169
12494
  const installCmd = getInstallCommand(options.packageManager);
12495
+ yield { type: "progress", message: `
12496
+ > ${installCmd}` };
12170
12497
  for await (const event of runCommandStreaming(installCwd, installCmd)) {
12171
12498
  if (event.type === "output") {
12172
12499
  yield { type: "progress", message: event.line };
@@ -12185,6 +12512,7 @@ async function* createScaffoldedProjectStreaming(options) {
12185
12512
  }
12186
12513
  }
12187
12514
  }
12515
+ await writeAgentsMd(scaffoldOptions.projectPath, template.agentsMd);
12188
12516
  yield { type: "result", result: { success: true, projectPath: scaffoldOptions.projectPath } };
12189
12517
  }
12190
12518
  function getScaffoldTemplates() {
@@ -13075,6 +13403,24 @@ var ProjectManagerImpl = class {
13075
13403
  }
13076
13404
  await this.metadataManager.saveProjects(projects);
13077
13405
  }
13406
+ /**
13407
+ * Updates the project's validation script
13408
+ * @param projectId - The project ID
13409
+ * @param validationScript - The validation script to run after chat responses (or null to clear)
13410
+ */
13411
+ async updateValidationScript(projectId, validationScript) {
13412
+ const projects = await this.metadataManager.loadProjects();
13413
+ const projectIndex = projects.findIndex((p) => p.id === projectId);
13414
+ if (projectIndex === -1) {
13415
+ throw new Error(`Project not found: ${projectId}`);
13416
+ }
13417
+ if (validationScript === null || validationScript === "") {
13418
+ delete projects[projectIndex].validationScript;
13419
+ } else {
13420
+ projects[projectIndex].validationScript = validationScript;
13421
+ }
13422
+ await this.metadataManager.saveProjects(projects);
13423
+ }
13078
13424
  /**
13079
13425
  * Updates the project's name
13080
13426
  * @param projectId - The project ID
@@ -13753,16 +14099,59 @@ async function postScaffold(c, projectManager) {
13753
14099
  }
13754
14100
  }
13755
14101
 
14102
+ // src/features/scaffold/scaffold-vp.route.ts
14103
+ init_utils();
14104
+ async function checkVp(c) {
14105
+ try {
14106
+ const { shell, args: args2 } = getShellConfig();
14107
+ const result = spawnSyncProcess(shell, [...args2, "vp help"], {
14108
+ env: getShellEnv(),
14109
+ encoding: "utf-8"
14110
+ });
14111
+ const installed = result.status === 0;
14112
+ return c.json({ installed });
14113
+ } catch (error) {
14114
+ console.error("Failed to check vp installation:", error);
14115
+ return c.json({ installed: false });
14116
+ }
14117
+ }
14118
+ async function installVp(c) {
14119
+ const isWindows = process.platform === "win32";
14120
+ const cmd = isWindows ? "irm https://vite.plus/ps1 | iex" : "curl -fsSL https://vite.plus | bash";
14121
+ try {
14122
+ const { shell, args: args2 } = getShellConfig();
14123
+ const result = spawnSyncProcess(shell, [...args2, cmd], {
14124
+ env: getShellEnv(),
14125
+ encoding: "utf-8"
14126
+ });
14127
+ if (result.status === 0) {
14128
+ return c.json({ success: true });
14129
+ } else {
14130
+ return c.json(
14131
+ { success: false, error: result.stderr?.toString() || "Installation failed" },
14132
+ 500
14133
+ );
14134
+ }
14135
+ } catch (error) {
14136
+ return c.json(
14137
+ { success: false, error: error instanceof Error ? error.message : String(error) },
14138
+ 500
14139
+ );
14140
+ }
14141
+ }
14142
+
13756
14143
  // src/features/scaffold/scaffold.routes.ts
13757
14144
  function createScaffoldRoutes(projectManager) {
13758
14145
  const router = new Hono9();
13759
14146
  router.get("/templates", (c) => getScaffoldTemplatesRoute(c));
14147
+ router.get("/check-vp", checkVp);
14148
+ router.post("/install-vp", installVp);
13760
14149
  router.post("/", (c) => postScaffold(c, projectManager));
13761
14150
  return router;
13762
14151
  }
13763
14152
 
13764
14153
  // src/features/slash-commands/slash-commands.manager.ts
13765
- import { readdir as readdir9, readFile as readFile11, mkdir as mkdir3, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
14154
+ import { readdir as readdir9, readFile as readFile11, mkdir as mkdir3, writeFile as writeFile3, unlink as unlink2 } from "fs/promises";
13766
14155
  import { join as join20, basename as basename3, extname as extname4 } from "path";
13767
14156
  import { existsSync as existsSync13 } from "fs";
13768
14157
  import { homedir as homedir7 } from "os";
@@ -13910,7 +14299,7 @@ var SlashCommandManager = class _SlashCommandManager {
13910
14299
  markdown += "---\n\n";
13911
14300
  }
13912
14301
  markdown += content;
13913
- await writeFile2(filePath, markdown, "utf-8");
14302
+ await writeFile3(filePath, markdown, "utf-8");
13914
14303
  return {
13915
14304
  name,
13916
14305
  content,
@@ -13944,7 +14333,7 @@ var SlashCommandManager = class _SlashCommandManager {
13944
14333
  markdown += "---\n\n";
13945
14334
  }
13946
14335
  markdown += content;
13947
- await writeFile2(filePath, markdown, "utf-8");
14336
+ await writeFile3(filePath, markdown, "utf-8");
13948
14337
  }
13949
14338
  /**
13950
14339
  * Delete a command
@@ -15535,7 +15924,25 @@ var buildStructuredContentFromEvents = (events, fallbackContent) => {
15535
15924
  if (event.type === "message" && event.role === "assistant" && typeof event.content === "string") {
15536
15925
  const assistantBlocks = parseAssistantContentBlocks(event.content);
15537
15926
  if (assistantBlocks && assistantBlocks.length > 0) {
15538
- return JSON.stringify(assistantBlocks);
15927
+ const toolResultBlocks = [];
15928
+ for (const ev of events) {
15929
+ if (ev.type === "message" && ev.role === "tool" && typeof ev.content === "string") {
15930
+ try {
15931
+ const parsed = JSON.parse(ev.content.trim());
15932
+ if (Array.isArray(parsed)) {
15933
+ for (const item of parsed) {
15934
+ if (item && typeof item === "object" && "type" in item && item.type === "tool-result") {
15935
+ toolResultBlocks.push(item);
15936
+ }
15937
+ }
15938
+ } else if (parsed && typeof parsed === "object" && "type" in parsed && parsed.type === "tool-result") {
15939
+ toolResultBlocks.push(parsed);
15940
+ }
15941
+ } catch {
15942
+ }
15943
+ }
15944
+ }
15945
+ return JSON.stringify([...assistantBlocks, ...toolResultBlocks]);
15539
15946
  }
15540
15947
  }
15541
15948
  }
@@ -15659,7 +16066,7 @@ async function handleListThreadFiles(c, threadManager) {
15659
16066
  }
15660
16067
 
15661
16068
  // src/features/threads/threads-explorer.route.ts
15662
- import { readdir as readdir11, readFile as readFile13, rename as rename2, writeFile as writeFile3, mkdir as mkdir5, rm as rm5, access as access4 } from "fs/promises";
16069
+ 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";
15663
16070
  import { join as join23 } from "path";
15664
16071
  import { spawn as spawn2 } from "child_process";
15665
16072
  var Utils3 = null;
@@ -15857,7 +16264,7 @@ async function handleCreateExplorerFile(c, threadManager) {
15857
16264
  absParent = validated;
15858
16265
  }
15859
16266
  await mkdir5(absParent, { recursive: true });
15860
- await writeFile3(join23(absParent, name), "", "utf-8");
16267
+ await writeFile4(join23(absParent, name), "", "utf-8");
15861
16268
  const relPath = dirPath ? `${dirPath}/${name}` : name;
15862
16269
  return c.json({ success: true, path: relPath });
15863
16270
  } catch (error) {
@@ -16124,12 +16531,12 @@ async function handleFixComments(c, threadManager) {
16124
16531
  }
16125
16532
 
16126
16533
  // src/features/threads/threads-ai-files.route.ts
16127
- import { readFile as readFile14, writeFile as writeFile5, mkdir as mkdir7, access as access5, rm as rm6 } from "fs/promises";
16534
+ import { readFile as readFile14, writeFile as writeFile6, mkdir as mkdir7, access as access5, rm as rm6 } from "fs/promises";
16128
16535
  import { join as join26 } from "path";
16129
16536
  import { existsSync as existsSync15 } from "fs";
16130
16537
 
16131
16538
  // src/features/git/git-download-folder.ts
16132
- import { mkdir as mkdir6, writeFile as writeFile4 } from "fs/promises";
16539
+ import { mkdir as mkdir6, writeFile as writeFile5 } from "fs/promises";
16133
16540
  import { join as join25, dirname as dirname4 } from "path";
16134
16541
  async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
16135
16542
  const { token, ref } = options;
@@ -16175,7 +16582,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
16175
16582
  }
16176
16583
  let content = Buffer.from(await fileRes.arrayBuffer()).toString("utf8");
16177
16584
  content = content.replace(/Claude/gi, "Tarsk");
16178
- await writeFile4(localPath, content, "utf8");
16585
+ await writeFile5(localPath, content, "utf8");
16179
16586
  console.log(` \u2713 ${relativePath}`);
16180
16587
  })
16181
16588
  );
@@ -16298,7 +16705,7 @@ async function handleSaveThreadAIFile(c, threadManager) {
16298
16705
  }
16299
16706
  const parentDir = join26(absPath, "..");
16300
16707
  await mkdir7(parentDir, { recursive: true });
16301
- await writeFile5(absPath, content, "utf-8");
16708
+ await writeFile6(absPath, content, "utf-8");
16302
16709
  return c.json({ success: true, path: filePath });
16303
16710
  } catch (error) {
16304
16711
  return errorResponse(
@@ -16399,7 +16806,7 @@ description: ${description}${toolsLine}
16399
16806
 
16400
16807
  Place your agent system prompt here as markdown. This will be used as the system prompt when this agent is invoked as a subagent.
16401
16808
  `;
16402
- await writeFile5(agentFileAbsPath, agentContent, "utf-8");
16809
+ await writeFile6(agentFileAbsPath, agentContent, "utf-8");
16403
16810
  return c.json({
16404
16811
  success: true,
16405
16812
  path: agentFileRelPath,
@@ -16464,7 +16871,7 @@ description: ${description}
16464
16871
 
16465
16872
  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.
16466
16873
  `;
16467
- await writeFile5(skillFileAbsPath, skillContent, "utf-8");
16874
+ await writeFile6(skillFileAbsPath, skillContent, "utf-8");
16468
16875
  return c.json({
16469
16876
  success: true,
16470
16877
  path: skillFileRelPath,
@@ -19833,7 +20240,7 @@ function createUpdateRoutes(updater) {
19833
20240
 
19834
20241
  // src/features/mcp/mcp.routes.ts
19835
20242
  import { Hono as Hono13 } from "hono";
19836
- import { readFile as readFile15, writeFile as writeFile6, mkdir as mkdir9, access as access6 } from "fs/promises";
20243
+ import { readFile as readFile15, writeFile as writeFile7, mkdir as mkdir9, access as access6 } from "fs/promises";
19837
20244
  import { join as join28, dirname as dirname6 } from "path";
19838
20245
 
19839
20246
  // src/features/mcp/mcp.popular.json
@@ -19961,7 +20368,7 @@ async function writeMCPConfig(projectPath, config) {
19961
20368
  const existing = await readMCPConfig(projectPath);
19962
20369
  const targetPath = existing?.filePath ?? join28(projectPath, ".agents/mcp.json");
19963
20370
  await mkdir9(dirname6(targetPath), { recursive: true });
19964
- await writeFile6(targetPath, JSON.stringify(config, null, 2), "utf-8");
20371
+ await writeFile7(targetPath, JSON.stringify(config, null, 2), "utf-8");
19965
20372
  }
19966
20373
  function createMCPRoutes() {
19967
20374
  const router = new Hono13();