@pikaa-ai/pikaa 0.3.18 → 0.3.20

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.
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  // src/cli/index.ts
5
5
  import { resolve as resolve21 } from "path";
6
- import { existsSync as existsSync22 } from "fs";
6
+ import { existsSync as existsSync23 } from "fs";
7
7
  import { createInterface } from "readline";
8
8
 
9
9
  // src/auth/store.ts
@@ -235,12 +235,15 @@ class DefaultModelClientSession {
235
235
  };
236
236
  return;
237
237
  }
238
- const messages = [
239
- {
240
- role: "system",
241
- content: params.systemPrompt
242
- }
243
- ];
238
+ const enablePromptCache = params.enablePromptCache ?? this.config.enablePromptCache ?? true;
239
+ const systemMessage = {
240
+ role: "system",
241
+ content: params.systemPrompt
242
+ };
243
+ if (enablePromptCache) {
244
+ systemMessage.cache_control = { type: "ephemeral" };
245
+ }
246
+ const messages = [systemMessage];
244
247
  for (let i = 0;i < params.history.length; i++) {
245
248
  const item = params.history[i];
246
249
  if (item.type === "user_message") {
@@ -324,14 +327,27 @@ class DefaultModelClientSession {
324
327
  temperature: params.temperature ?? 0.2
325
328
  };
326
329
  if (toolsPayload && toolsPayload.length > 0) {
327
- body.tools = toolsPayload;
330
+ if (enablePromptCache && toolsPayload.length > 0) {
331
+ const clonedTools = [...toolsPayload];
332
+ const lastIdx = clonedTools.length - 1;
333
+ if (clonedTools[lastIdx] && typeof clonedTools[lastIdx] === "object") {
334
+ clonedTools[lastIdx] = {
335
+ ...clonedTools[lastIdx],
336
+ cache_control: { type: "ephemeral" }
337
+ };
338
+ }
339
+ body.tools = clonedTools;
340
+ } else {
341
+ body.tools = toolsPayload;
342
+ }
328
343
  body.tool_choice = "auto";
329
344
  }
330
345
  const maxRetries = params.maxRetries ?? this.config.maxRetries ?? 10;
331
346
  let attempt = 0;
332
347
  let response = null;
333
348
  const headers = {
334
- "Content-Type": "application/json"
349
+ "Content-Type": "application/json",
350
+ "anthropic-beta": "prompt-caching-2024-07-25"
335
351
  };
336
352
  if (apiKey) {
337
353
  headers["Authorization"] = `Bearer ${apiKey}`;
@@ -441,7 +457,8 @@ class DefaultModelClientSession {
441
457
  type: "done",
442
458
  inputTokens: usageMetrics.inputTokens,
443
459
  outputTokens: usageMetrics.outputTokens,
444
- totalTokens: usageMetrics.totalTokens
460
+ totalTokens: usageMetrics.totalTokens,
461
+ cachedTokens: usageMetrics.cachedTokens
445
462
  };
446
463
  return;
447
464
  }
@@ -452,10 +469,12 @@ class DefaultModelClientSession {
452
469
  continue;
453
470
  }
454
471
  if (parsed.usage) {
472
+ const cached = parsed.usage.prompt_tokens_details?.cached_tokens ?? parsed.usage.cache_read_input_tokens ?? parsed.usage.cached_content_token_count ?? 0;
455
473
  usageMetrics = {
456
474
  inputTokens: parsed.usage.prompt_tokens,
457
475
  outputTokens: parsed.usage.completion_tokens,
458
- totalTokens: parsed.usage.total_tokens
476
+ totalTokens: parsed.usage.total_tokens,
477
+ cachedTokens: cached > 0 ? cached : undefined
459
478
  };
460
479
  }
461
480
  const choice = parsed.choices?.[0];
@@ -673,6 +692,8 @@ class TurnAbortedError extends GroupyError {
673
692
  }
674
693
 
675
694
  // src/context/compactor.ts
695
+ var DEFAULT_MAX_CONTEXT_TOKENS = 256000;
696
+ var DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS = 180000;
676
697
  function estimateItemTokens(item) {
677
698
  const text = item.type === "user_message" ? item.content : item.type === "agent_message" ? item.content : item.type === "reasoning" ? item.content : item.type === "function_call" ? JSON.stringify(item.arguments) : item.type === "function_call_output" ? item.output : "";
678
699
  return Math.ceil(text.length / 4) + 4;
@@ -742,9 +763,11 @@ async function captureWorldState(cwd) {
742
763
  };
743
764
  }
744
765
  function formatWorldStatePrompt(state) {
766
+ const isWindows = process.platform === "win32";
767
+ const platformNote = isWindows ? `Platform: Windows (${state.os}). Shell is cmd.exe / PowerShell. POSIX commands (grep, find, cat, sed, awk) are NOT supported in shell. ALWAYS use native grep_search, find_files, and read_file tools.` : `Platform: ${state.os}`;
745
768
  const parts = [
746
769
  `Current Working Directory: ${state.cwd}`,
747
- `Platform: ${state.os}`
770
+ platformNote
748
771
  ];
749
772
  if (state.gitBranch) {
750
773
  parts.push(`Git Branch: ${state.gitBranch}`);
@@ -884,80 +907,205 @@ class AgentsMdLoader {
884
907
  var globalAgentsMdLoader = new AgentsMdLoader;
885
908
 
886
909
  // src/context/instructions.ts
887
- function buildSystemPrompt(params) {
888
- const sections = [];
910
+ function wrapXmlTag(tag, content, attrs = {}) {
911
+ const attrStr = Object.entries(attrs).filter(([_, v]) => Boolean(v)).map(([k, v]) => ` ${k}="${v}"`).join("");
912
+ return `<${tag}${attrStr}>
913
+ ${content.trim()}
914
+ </${tag}>`;
915
+ }
916
+ function buildStructuredSystemPrompt(params) {
889
917
  const cwd = params.cwd || process.cwd();
890
918
  const mode = params.collaborationMode || "default";
891
- if (params.basePrompt) {
892
- sections.push(params.basePrompt);
893
- } else {
919
+ const blocks = [];
920
+ let baseContent = params.basePrompt;
921
+ if (!baseContent) {
894
922
  const templateName = params.basePromptTemplate || "base/groupy_prompt.md";
895
- const baseContent = globalPromptLoader.loadTemplate(templateName, {}, cwd);
896
- if (baseContent) {
897
- sections.push(baseContent.trim());
898
- } else {
899
- sections.push("You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.");
900
- }
923
+ baseContent = globalPromptLoader.loadTemplate(templateName, {}, cwd) || "You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.";
901
924
  }
925
+ blocks.push({
926
+ tag: "system_identity",
927
+ content: wrapXmlTag("system_identity", baseContent),
928
+ cacheable: true
929
+ });
902
930
  if (params.personality) {
903
931
  const personalityContent = globalPromptLoader.loadTemplate(`personalities/${params.personality}.md`, {}, cwd);
904
932
  if (personalityContent) {
905
- sections.push(personalityContent.trim());
933
+ blocks.push({
934
+ tag: "personality",
935
+ content: wrapXmlTag("personality", personalityContent, { kind: params.personality }),
936
+ cacheable: true
937
+ });
906
938
  }
907
939
  }
908
940
  if (params.isOrchestrator) {
909
941
  const orchestratorContent = globalPromptLoader.loadTemplate("agents/orchestrator.md", {}, cwd);
910
942
  if (orchestratorContent) {
911
- sections.push(orchestratorContent.trim());
943
+ blocks.push({
944
+ tag: "orchestrator_guidelines",
945
+ content: wrapXmlTag("orchestrator_guidelines", orchestratorContent),
946
+ cacheable: true
947
+ });
912
948
  }
913
949
  }
914
- const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, {
915
- KNOWN_MODE_NAMES: "default, plan, review"
916
- }, cwd);
950
+ const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, { KNOWN_MODE_NAMES: "default, plan, review" }, cwd);
917
951
  if (modeTemplate) {
918
- sections.push(modeTemplate.trim());
952
+ blocks.push({
953
+ tag: "collaboration_mode",
954
+ content: wrapXmlTag("collaboration_mode", modeTemplate, { name: mode }),
955
+ cacheable: true
956
+ });
919
957
  }
920
958
  if (params.sandboxMode) {
921
- const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, {
922
- network_access: params.networkAccess ? "enabled" : "disabled"
923
- }, cwd);
959
+ const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, { network_access: params.networkAccess ? "enabled" : "disabled" }, cwd);
924
960
  if (sandboxTemplate) {
925
- sections.push(sandboxTemplate.trim());
961
+ blocks.push({
962
+ tag: "sandbox_policy",
963
+ content: wrapXmlTag("sandbox_policy", sandboxTemplate, { mode: params.sandboxMode }),
964
+ cacheable: true
965
+ });
926
966
  }
927
967
  }
928
968
  if (params.approvalPolicy) {
929
969
  const approvalTemplate = globalPromptLoader.loadTemplate(`permissions/approval_policy/${params.approvalPolicy}.md`, {}, cwd);
930
970
  if (approvalTemplate) {
931
- sections.push(approvalTemplate.trim());
971
+ blocks.push({
972
+ tag: "approval_policy",
973
+ content: wrapXmlTag("approval_policy", approvalTemplate, { policy: params.approvalPolicy }),
974
+ cacheable: true
975
+ });
932
976
  }
933
977
  }
934
978
  const projectInstructions = globalAgentsMdLoader.loadProjectInstructions(cwd);
935
979
  if (projectInstructions) {
936
- sections.push(`## Project Instructions (AGENTS.md)
937
-
938
- ${projectInstructions.content.trim()}`);
980
+ blocks.push({
981
+ tag: "project_instructions",
982
+ content: wrapXmlTag("project_instructions", projectInstructions.content, { source: "AGENTS.md" }),
983
+ cacheable: true
984
+ });
939
985
  }
940
986
  if (params.memoriesPrompt) {
941
- sections.push(params.memoriesPrompt.trim());
987
+ blocks.push({
988
+ tag: "persistent_memories",
989
+ content: wrapXmlTag("persistent_memories", params.memoriesPrompt),
990
+ cacheable: true
991
+ });
942
992
  }
943
993
  if (params.skillsPrompt) {
944
- sections.push(params.skillsPrompt.trim());
994
+ blocks.push({
995
+ tag: "domain_skills",
996
+ content: wrapXmlTag("domain_skills", params.skillsPrompt),
997
+ cacheable: true
998
+ });
945
999
  }
946
1000
  if (params.mcpPrompt) {
947
- sections.push(params.mcpPrompt.trim());
1001
+ blocks.push({
1002
+ tag: "mcp_servers",
1003
+ content: wrapXmlTag("mcp_servers", params.mcpPrompt),
1004
+ cacheable: true
1005
+ });
948
1006
  }
949
1007
  if (params.developerInstructions) {
950
- sections.push(`## Developer Instructions
951
- ${params.developerInstructions}`);
1008
+ blocks.push({
1009
+ tag: "developer_instructions",
1010
+ content: wrapXmlTag("developer_instructions", params.developerInstructions),
1011
+ cacheable: true
1012
+ });
952
1013
  }
1014
+ const dynamicBlocks = [];
953
1015
  if (params.worldStatePrompt) {
954
- sections.push(`## Environment Context
955
- ${params.worldStatePrompt}`);
1016
+ dynamicBlocks.push({
1017
+ tag: "runtime_environment",
1018
+ content: wrapXmlTag("runtime_environment", params.worldStatePrompt),
1019
+ cacheable: false
1020
+ });
956
1021
  }
957
- return sections.join(`
1022
+ const staticPrefix = blocks.map((b) => b.content).join(`
958
1023
 
959
1024
  `);
1025
+ const dynamicSuffix = dynamicBlocks.map((b) => b.content).join(`
1026
+
1027
+ `);
1028
+ const allBlocks = [...blocks, ...dynamicBlocks];
1029
+ const text = allBlocks.map((b) => b.content).join(`
1030
+
1031
+ `);
1032
+ return {
1033
+ text,
1034
+ staticPrefix,
1035
+ dynamicSuffix,
1036
+ blocks: allBlocks
1037
+ };
1038
+ }
1039
+ function buildSystemPrompt(params) {
1040
+ return buildStructuredSystemPrompt(params).text;
1041
+ }
1042
+
1043
+ // src/workspace/ephemeral.ts
1044
+ import { mkdirSync as mkdirSync3, rmSync, existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1045
+ import { join as join4 } from "path";
1046
+ import { tmpdir } from "os";
1047
+
1048
+ class EphemeralWorkspaceManager {
1049
+ activeScratchpads = new Set;
1050
+ createScratchpad(turnId) {
1051
+ const uniqueId = `groupy_scratch_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1052
+ const baseDir = join4(tmpdir(), "groupy-ephemeral", turnId ? `turn_${turnId}` : "general");
1053
+ const scratchPath = join4(baseDir, uniqueId);
1054
+ mkdirSync3(scratchPath, { recursive: true });
1055
+ this.activeScratchpads.add(scratchPath);
1056
+ return scratchPath;
1057
+ }
1058
+ cleanup(scratchPath) {
1059
+ if (!scratchPath || !this.activeScratchpads.has(scratchPath))
1060
+ return;
1061
+ try {
1062
+ if (existsSync5(scratchPath)) {
1063
+ rmSync(scratchPath, { recursive: true, force: true });
1064
+ }
1065
+ } catch {} finally {
1066
+ this.activeScratchpads.delete(scratchPath);
1067
+ }
1068
+ }
1069
+ cleanupTurn(turnId) {
1070
+ const turnDir = join4(tmpdir(), "groupy-ephemeral", `turn_${turnId}`);
1071
+ try {
1072
+ if (existsSync5(turnDir)) {
1073
+ rmSync(turnDir, { recursive: true, force: true });
1074
+ }
1075
+ } catch {}
1076
+ }
1077
+ cleanRootResidue(cwd) {
1078
+ const cleaned = [];
1079
+ try {
1080
+ const files = readdirSync2(cwd);
1081
+ const tempPatterns = [
1082
+ /^tmp_/i,
1083
+ /^draft_/i,
1084
+ /^scratch_/i,
1085
+ /^temp_/i,
1086
+ /^preview_.*\.html$/i,
1087
+ /\.tmp$/i,
1088
+ /\.bak$/i,
1089
+ /~$/i
1090
+ ];
1091
+ for (const file of files) {
1092
+ if (tempPatterns.some((p) => p.test(file))) {
1093
+ const fullPath = join4(cwd, file);
1094
+ const stat = statSync2(fullPath);
1095
+ if (stat.isFile()) {
1096
+ rmSync(fullPath, { force: true });
1097
+ cleaned.push(file);
1098
+ } else if (stat.isDirectory() && (file.startsWith("tmp_") || file.startsWith("scratch_") || file.startsWith("temp_"))) {
1099
+ rmSync(fullPath, { recursive: true, force: true });
1100
+ cleaned.push(file);
1101
+ }
1102
+ }
1103
+ }
1104
+ } catch {}
1105
+ return cleaned;
1106
+ }
960
1107
  }
1108
+ var globalEphemeralWorkspace = new EphemeralWorkspaceManager;
961
1109
 
962
1110
  // src/session/turn.ts
963
1111
  async function runTurn(session, turnContext, input) {
@@ -968,7 +1116,7 @@ async function runTurn(session, turnContext, input) {
968
1116
  });
969
1117
  const currentHistory = session.getHistory();
970
1118
  const estimatedTokens = estimateTotalTokens(currentHistory);
971
- const maxTokenLimit = 80000;
1119
+ const maxTokenLimit = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS;
972
1120
  if (estimatedTokens > maxTokenLimit) {
973
1121
  const compacted = compactHistory(currentHistory);
974
1122
  session.setHistory(compacted);
@@ -1007,6 +1155,7 @@ async function runTurn(session, turnContext, input) {
1007
1155
  let iteration = 0;
1008
1156
  let accumulatedInputTokens = 0;
1009
1157
  let accumulatedOutputTokens = 0;
1158
+ let accumulatedCachedTokens = 0;
1010
1159
  const clientSession = session.modelClient.newSession();
1011
1160
  try {
1012
1161
  while (iteration < turnContext.maxIterations) {
@@ -1023,7 +1172,8 @@ async function runTurn(session, turnContext, input) {
1023
1172
  systemPrompt: effectiveSystemPrompt,
1024
1173
  history: session.getHistory(),
1025
1174
  tools: turnContext.tools,
1026
- signal
1175
+ signal,
1176
+ enablePromptCache: true
1027
1177
  });
1028
1178
  for await (const chunk of stream) {
1029
1179
  if (signal.aborted) {
@@ -1049,6 +1199,8 @@ async function runTurn(session, turnContext, input) {
1049
1199
  iterInputTokens = chunk.inputTokens;
1050
1200
  if (chunk.outputTokens !== undefined)
1051
1201
  iterOutputTokens = chunk.outputTokens;
1202
+ if (chunk.cachedTokens !== undefined)
1203
+ accumulatedCachedTokens += chunk.cachedTokens;
1052
1204
  } else if (chunk.type === "error") {
1053
1205
  throw chunk.error;
1054
1206
  }
@@ -1160,7 +1312,10 @@ async function runTurn(session, turnContext, input) {
1160
1312
  session.addHistoryItem({
1161
1313
  id: `msg_nudge_${Date.now()}`,
1162
1314
  type: "user_message",
1163
- content: "Please proceed with executing the task. Provide your complete analysis or call the required tools now.",
1315
+ content: `[Systematic ReAct Nudge]: No tool actions or answers were produced in this iteration.
1316
+ 1. Review the user's objective and determine the immediate next action.
1317
+ 2. If more context is required, invoke an exploration tool ('read_file', 'list_dir', 'grep_search', 'find_files').
1318
+ 3. If ready to answer or implement, call the required mutating tool or deliver your full, concrete response now.`,
1164
1319
  createdAt: Date.now()
1165
1320
  });
1166
1321
  continue;
@@ -1169,13 +1324,14 @@ async function runTurn(session, turnContext, input) {
1169
1324
  break;
1170
1325
  }
1171
1326
  const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
1172
- const maxContextTokens = 128000;
1327
+ const maxContextTokens = DEFAULT_MAX_CONTEXT_TOKENS;
1173
1328
  session.emitEvent({
1174
1329
  type: "TurnCompleted",
1175
1330
  turnId,
1176
1331
  inputTokens: accumulatedInputTokens,
1177
1332
  outputTokens: accumulatedOutputTokens,
1178
1333
  totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
1334
+ cachedTokens: accumulatedCachedTokens > 0 ? accumulatedCachedTokens : undefined,
1179
1335
  contextTokens: totalContextTokens,
1180
1336
  maxContextTokens
1181
1337
  });
@@ -1190,6 +1346,8 @@ async function runTurn(session, turnContext, input) {
1190
1346
  });
1191
1347
  } finally {
1192
1348
  session.clearActiveTurn(turnId);
1349
+ globalEphemeralWorkspace.cleanupTurn(turnId);
1350
+ globalEphemeralWorkspace.cleanRootResidue(turnContext.environment.cwd);
1193
1351
  }
1194
1352
  }
1195
1353
 
@@ -1289,9 +1447,9 @@ class ExecPolicy {
1289
1447
  shouldPromptFileEdit(filePath) {
1290
1448
  if (this.mode === "plan") {
1291
1449
  return {
1292
- prompt: false,
1450
+ prompt: true,
1293
1451
  isPlanBlocked: true,
1294
- reason: "Plan Mode is active. Mutating files is not allowed while planning."
1452
+ reason: `[Plan Mode Gate] Approval required to mutate '${filePath || "file"}' and proceed with implementation.`
1295
1453
  };
1296
1454
  }
1297
1455
  if (this.mode === "manual") {
@@ -1305,11 +1463,14 @@ class ExecPolicy {
1305
1463
  evaluate(command) {
1306
1464
  const trimmed = command.trim();
1307
1465
  if (this.mode === "plan") {
1308
- const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
1466
+ const isReadOnly = /^(git\s+(status|log|diff|branch|show|rev-parse)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
1309
1467
  if (isReadOnly) {
1310
1468
  return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
1311
1469
  }
1312
- return { decision: "deny", reason: "Cannot execute mutating shell commands in Plan mode" };
1470
+ return {
1471
+ decision: "prompt",
1472
+ reason: `[Plan Mode Gate] Approval required to execute shell command '${trimmed}' in Plan Mode`
1473
+ };
1313
1474
  }
1314
1475
  if (this.mode === "manual") {
1315
1476
  return {
@@ -1580,9 +1741,9 @@ class Session {
1580
1741
  }
1581
1742
  }
1582
1743
  // src/tools/handlers/apply-patch.ts
1583
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1744
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1584
1745
  import { resolve as resolve5, dirname as dirname3 } from "path";
1585
- import { mkdirSync as mkdirSync3 } from "fs";
1746
+ import { mkdirSync as mkdirSync4 } from "fs";
1586
1747
  var applyPatchTool = {
1587
1748
  name: "apply_patch",
1588
1749
  description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
@@ -1614,34 +1775,40 @@ var applyPatchTool = {
1614
1775
  const replacementContent = String(args.replacementContent ?? "");
1615
1776
  if (ctx.execPolicy) {
1616
1777
  const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
1617
- if (evalResult.isPlanBlocked || ctx.mode === "plan") {
1618
- return {
1619
- output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
1620
- isError: true
1621
- };
1622
- }
1623
1778
  if (evalResult.prompt && ctx.requestApproval) {
1624
- const approval = await ctx.requestApproval(`Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
1779
+ const approval = await ctx.requestApproval(evalResult.reason || `Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
1625
1780
  const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
1626
1781
  if (!allowed) {
1627
- return { output: `Action rejected by user: apply_patch '${rawPath}'`, isError: true };
1782
+ return {
1783
+ output: `[Plan Mode Gate]: File modification declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
1784
+ isError: true
1785
+ };
1628
1786
  }
1787
+ } else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
1788
+ return {
1789
+ output: `[Plan Mode Gate]: Cannot mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
1790
+ isError: true
1791
+ };
1629
1792
  }
1630
1793
  }
1631
- if (!existsSync5(filePath)) {
1794
+ if (!existsSync6(filePath)) {
1632
1795
  if (targetContent) {
1633
1796
  return {
1634
- output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.`,
1797
+ output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.
1798
+ [Systematic Error Recovery Checklist]:
1799
+ 1. Root Cause: Trying to patch a non-existent file with targetContent.
1800
+ 2. Fix: For creating new files, leave 'targetContent' empty and provide full contents in 'replacementContent'.
1801
+ 3. Alternatively, check if the file path '${rawPath}' was mistyped.`,
1635
1802
  isError: true
1636
1803
  };
1637
1804
  }
1638
1805
  try {
1639
- mkdirSync3(dirname3(filePath), { recursive: true });
1806
+ mkdirSync4(dirname3(filePath), { recursive: true });
1640
1807
  writeFileSync2(filePath, replacementContent, "utf8");
1641
1808
  return { output: `Successfully created new file '${rawPath}'` };
1642
1809
  } catch (err) {
1643
1810
  return {
1644
- output: `Failed to create file: ${err instanceof Error ? err.message : String(err)}`,
1811
+ output: `Failed to create file '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
1645
1812
  isError: true
1646
1813
  };
1647
1814
  }
@@ -1650,21 +1817,33 @@ var applyPatchTool = {
1650
1817
  const originalFileContent = readFileSync4(filePath, "utf8");
1651
1818
  if (!targetContent) {
1652
1819
  return {
1653
- output: `Error: File '${rawPath}' already exists. Specify targetContent to replace specific lines or use overwrite.`,
1820
+ output: `Error: File '${rawPath}' already exists, but targetContent was empty.
1821
+ [Systematic Error Recovery Checklist]:
1822
+ 1. Root Cause: An existing file requires targetContent to specify which lines to replace.
1823
+ 2. Fix: Call 'read_file' on '${rawPath}', extract the exact target lines, and provide them in 'targetContent'.
1824
+ 3. To overwrite the whole file, use the 'write_file' tool instead.`,
1654
1825
  isError: true
1655
1826
  };
1656
1827
  }
1657
1828
  const firstIndex = originalFileContent.indexOf(targetContent);
1658
1829
  if (firstIndex === -1) {
1659
1830
  return {
1660
- output: `Error: targetContent was not found in '${rawPath}'. Please verify file contents before editing.`,
1831
+ output: `Error: targetContent was not found in '${rawPath}'.
1832
+ [Systematic Error Recovery Checklist]:
1833
+ 1. Root Cause: The snippet in targetContent does not match the actual file content (differences in whitespace, indentation, line endings, or prior edits).
1834
+ 2. Action: Call 'read_file' on '${rawPath}' to inspect current exact lines and indentation.
1835
+ 3. Fix: Provide the exact matching lines (including leading spaces) or wider context, then retry 'apply_patch'.`,
1661
1836
  isError: true
1662
1837
  };
1663
1838
  }
1664
1839
  const secondIndex = originalFileContent.indexOf(targetContent, firstIndex + 1);
1665
1840
  if (secondIndex !== -1) {
1666
1841
  return {
1667
- output: `Error: targetContent matched multiple locations in '${rawPath}'. Provide more surrounding context lines to ensure uniqueness.`,
1842
+ output: `Error: targetContent matched multiple locations in '${rawPath}'.
1843
+ [Systematic Error Recovery Checklist]:
1844
+ 1. Root Cause: targetContent is ambiguous and occurs multiple times in the file.
1845
+ 2. Action: Include 2-3 additional surrounding lines (before or after the target block) to make the target snippet uniquely identifiable.
1846
+ 3. Fix: Re-run 'apply_patch' with the extended unique block.`,
1668
1847
  isError: true
1669
1848
  };
1670
1849
  }
@@ -1675,7 +1854,7 @@ var applyPatchTool = {
1675
1854
  };
1676
1855
  } catch (err) {
1677
1856
  return {
1678
- output: `Failed to apply patch: ${err instanceof Error ? err.message : String(err)}`,
1857
+ output: `Failed to apply patch to '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
1679
1858
  isError: true
1680
1859
  };
1681
1860
  }
@@ -1762,7 +1941,7 @@ class WindowsSandbox {
1762
1941
  }
1763
1942
 
1764
1943
  // src/security/kernel/linux.ts
1765
- import { existsSync as existsSync6 } from "fs";
1944
+ import { existsSync as existsSync7 } from "fs";
1766
1945
 
1767
1946
  class LinuxSandbox {
1768
1947
  hasBwrap = false;
@@ -1773,7 +1952,7 @@ class LinuxSandbox {
1773
1952
  if (process.platform !== "linux") {
1774
1953
  return;
1775
1954
  }
1776
- this.hasBwrap = existsSync6("/usr/bin/bwrap") || existsSync6("/bin/bwrap") || existsSync6("/usr/local/bin/bwrap");
1955
+ this.hasBwrap = existsSync7("/usr/bin/bwrap") || existsSync7("/bin/bwrap") || existsSync7("/usr/local/bin/bwrap");
1777
1956
  }
1778
1957
  wrapCommand(cmd, profile) {
1779
1958
  if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
@@ -1807,7 +1986,7 @@ class LinuxSandbox {
1807
1986
  }
1808
1987
 
1809
1988
  // src/security/kernel/macos.ts
1810
- import { existsSync as existsSync7 } from "fs";
1989
+ import { existsSync as existsSync8 } from "fs";
1811
1990
 
1812
1991
  class MacOSSandbox {
1813
1992
  hasSandboxExec = false;
@@ -1818,7 +1997,7 @@ class MacOSSandbox {
1818
1997
  if (process.platform !== "darwin") {
1819
1998
  return;
1820
1999
  }
1821
- this.hasSandboxExec = existsSync7("/usr/bin/sandbox-exec");
2000
+ this.hasSandboxExec = existsSync8("/usr/bin/sandbox-exec");
1822
2001
  }
1823
2002
  generateProfile(profile) {
1824
2003
  const rules = [
@@ -1928,7 +2107,7 @@ var globalKernelSandbox = new KernelSandboxManager;
1928
2107
 
1929
2108
  // src/storage/prefix-rules-store.ts
1930
2109
  import { Database } from "bun:sqlite";
1931
- import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2110
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
1932
2111
  import { dirname as dirname4, resolve as resolve7 } from "path";
1933
2112
  class PrefixRulesStore {
1934
2113
  db;
@@ -1939,8 +2118,8 @@ class PrefixRulesStore {
1939
2118
  const effectivePath = dbOrPath || getPrefixRulesDbPath();
1940
2119
  if (effectivePath !== ":memory:") {
1941
2120
  const dir = dirname4(effectivePath);
1942
- if (!existsSync8(dir)) {
1943
- mkdirSync4(dir, { recursive: true });
2121
+ if (!existsSync9(dir)) {
2122
+ mkdirSync5(dir, { recursive: true });
1944
2123
  }
1945
2124
  }
1946
2125
  this.db = new Database(effectivePath);
@@ -2129,6 +2308,7 @@ function createShellTool(policy = new ExecPolicy) {
2129
2308
  const timeoutMs = typeof args.timeoutMs === "number" ? args.timeoutMs : 30000;
2130
2309
  const isWindows = process.platform === "win32";
2131
2310
  const baseCmd = isWindows ? ["cmd.exe", "/d", "/s", "/c", command] : ["/bin/sh", "-c", command];
2311
+ const ephemeralScratchpad = globalEphemeralWorkspace.createScratchpad(ctx.turnId);
2132
2312
  const sandboxProfile = globalKernelSandbox.buildDefaultProfile(ctx.cwd);
2133
2313
  if (isEscalated) {
2134
2314
  sandboxProfile.allowNetwork = true;
@@ -2139,6 +2319,10 @@ function createShellTool(policy = new ExecPolicy) {
2139
2319
  cwd: ctx.cwd,
2140
2320
  env: {
2141
2321
  ...process.env,
2322
+ TMPDIR: ephemeralScratchpad,
2323
+ TEMP: ephemeralScratchpad,
2324
+ TMP: ephemeralScratchpad,
2325
+ GROUPY_SCRATCH_DIR: ephemeralScratchpad,
2142
2326
  ...ctx.proxyEnv
2143
2327
  },
2144
2328
  stdout: "pipe",
@@ -2175,9 +2359,14 @@ function createShellTool(policy = new ExecPolicy) {
2175
2359
  if (result.stderr)
2176
2360
  outputParts.push(`STDERR:
2177
2361
  ${result.stderr.trim()}`);
2178
- if (result.code !== 0)
2362
+ if (result.code !== 0) {
2179
2363
  outputParts.push(`
2180
- [Process exited with code ${result.code}]`);
2364
+ [Process exited with non-zero code ${result.code}]`);
2365
+ outputParts.push(`[Systematic Error Recovery Checklist]:
2366
+ 1. Inspect STDERR above to pinpoint syntax errors, failed test assertions, or missing dependencies.
2367
+ 2. If this is a test failure, trace the failure in source code and fix the root cause before re-running.
2368
+ 3. If this is a missing command/module, install or configure the prerequisite.`);
2369
+ }
2181
2370
  const output = outputParts.join(`
2182
2371
  `) || "[Command completed with no output]";
2183
2372
  return {
@@ -2186,16 +2375,21 @@ ${result.stderr.trim()}`);
2186
2375
  };
2187
2376
  } catch (err) {
2188
2377
  return {
2189
- output: `Execution error: ${err instanceof Error ? err.message : String(err)}`,
2378
+ output: `Execution error: ${err instanceof Error ? err.message : String(err)}
2379
+ [Systematic Error Recovery Checklist]:
2380
+ 1. Verify command syntax, arguments, and executable availability in PATH.
2381
+ 2. Check if the current working directory ('${ctx.cwd}') is valid.`,
2190
2382
  isError: true
2191
2383
  };
2384
+ } finally {
2385
+ globalEphemeralWorkspace.cleanup(ephemeralScratchpad);
2192
2386
  }
2193
2387
  }
2194
2388
  };
2195
2389
  }
2196
2390
  var shellTool = createShellTool();
2197
2391
  // src/tools/handlers/file-ops.ts
2198
- import { readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync9, statSync as statSync2, mkdirSync as mkdirSync5 } from "fs";
2392
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync10, statSync as statSync3, mkdirSync as mkdirSync6 } from "fs";
2199
2393
  import { resolve as resolve8, dirname as dirname5 } from "path";
2200
2394
  var readFileTool = {
2201
2395
  name: "read_file",
@@ -2209,7 +2403,7 @@ var readFileTool = {
2209
2403
  },
2210
2404
  async execute(args, ctx) {
2211
2405
  const filePath = resolve8(ctx.cwd, String(args.path || ""));
2212
- if (!existsSync9(filePath)) {
2406
+ if (!existsSync10(filePath)) {
2213
2407
  return { output: `Error: File not found: '${args.path}'`, isError: true };
2214
2408
  }
2215
2409
  try {
@@ -2231,14 +2425,14 @@ var listDirTool = {
2231
2425
  },
2232
2426
  async execute(args, ctx) {
2233
2427
  const dirPath = resolve8(ctx.cwd, String(args.path || "."));
2234
- if (!existsSync9(dirPath)) {
2428
+ if (!existsSync10(dirPath)) {
2235
2429
  return { output: `Error: Directory not found: '${args.path}'`, isError: true };
2236
2430
  }
2237
2431
  try {
2238
- const entries = readdirSync2(dirPath);
2432
+ const entries = readdirSync3(dirPath);
2239
2433
  const formatted = entries.map((entry) => {
2240
2434
  const full = resolve8(dirPath, entry);
2241
- const isDir = statSync2(full).isDirectory();
2435
+ const isDir = statSync3(full).isDirectory();
2242
2436
  return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
2243
2437
  });
2244
2438
  return { output: formatted.join(`
@@ -2264,26 +2458,35 @@ var writeFileTool = {
2264
2458
  const filePath = resolve8(ctx.cwd, rawPath);
2265
2459
  if (ctx.execPolicy) {
2266
2460
  const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
2267
- if (evalResult.isPlanBlocked || ctx.mode === "plan") {
2268
- return {
2269
- output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
2270
- isError: true
2271
- };
2272
- }
2273
2461
  if (evalResult.prompt && ctx.requestApproval) {
2274
- const approval = await ctx.requestApproval(`Write file: ${rawPath}`, `write_file ${rawPath}`);
2462
+ const approval = await ctx.requestApproval(evalResult.reason || `Write file: ${rawPath}`, `write_file ${rawPath}`);
2275
2463
  const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
2276
2464
  if (!allowed) {
2277
- return { output: `Action rejected by user: write_file '${rawPath}'`, isError: true };
2465
+ return {
2466
+ output: `[Plan Mode Gate]: File creation declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
2467
+ isError: true
2468
+ };
2278
2469
  }
2470
+ } else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
2471
+ return {
2472
+ output: `[Plan Mode Gate]: Cannot write or mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
2473
+ isError: true
2474
+ };
2279
2475
  }
2280
2476
  }
2281
2477
  try {
2282
- mkdirSync5(dirname5(filePath), { recursive: true });
2478
+ mkdirSync6(dirname5(filePath), { recursive: true });
2283
2479
  writeFileSync3(filePath, String(args.content ?? ""), "utf8");
2284
2480
  return { output: `Successfully wrote to '${args.path}'` };
2285
2481
  } catch (err) {
2286
- return { output: `Failed to write file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
2482
+ return {
2483
+ output: `Failed to write file '${rawPath}': ${err instanceof Error ? err.message : String(err)}
2484
+ [Systematic Error Recovery Checklist]:
2485
+ 1. Check directory permissions and ensure the path is valid within the workspace.
2486
+ 2. If the path contains non-existent nested folders, they should be auto-created.
2487
+ 3. Verify that the file is not locked by another active process.`,
2488
+ isError: true
2489
+ };
2287
2490
  }
2288
2491
  }
2289
2492
  };
@@ -2402,8 +2605,8 @@ var updatePlanTool = {
2402
2605
  }
2403
2606
  };
2404
2607
  // src/search/engine.ts
2405
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3, existsSync as existsSync10 } from "fs";
2406
- import { resolve as resolve9, relative, join as join4, extname } from "path";
2608
+ import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4, existsSync as existsSync11 } from "fs";
2609
+ import { resolve as resolve9, relative, join as join5, extname } from "path";
2407
2610
  var DEFAULT_IGNORE_DIRS = new Set([
2408
2611
  ".git",
2409
2612
  "node_modules",
@@ -2449,7 +2652,7 @@ var BINARY_EXTENSIONS = new Set([
2449
2652
  class FileSearchEngine {
2450
2653
  grep(cwd, options) {
2451
2654
  const searchRoot = resolve9(cwd, options.path || ".");
2452
- if (!existsSync10(searchRoot)) {
2655
+ if (!existsSync11(searchRoot)) {
2453
2656
  return { matches: [], totalMatches: 0, truncated: false };
2454
2657
  }
2455
2658
  const maxResults = options.maxResults || 50;
@@ -2499,7 +2702,7 @@ class FileSearchEngine {
2499
2702
  }
2500
2703
  findFiles(cwd, options) {
2501
2704
  const searchRoot = resolve9(cwd, options.path || ".");
2502
- if (!existsSync10(searchRoot))
2705
+ if (!existsSync11(searchRoot))
2503
2706
  return [];
2504
2707
  const maxResults = options.maxResults || 100;
2505
2708
  const gitignoreRules = this.loadGitignoreRules(searchRoot);
@@ -2538,8 +2741,8 @@ class FileSearchEngine {
2538
2741
  }
2539
2742
  loadGitignoreRules(root) {
2540
2743
  const rules = new Set;
2541
- const gitignorePath = join4(root, ".gitignore");
2542
- if (existsSync10(gitignorePath)) {
2744
+ const gitignorePath = join5(root, ".gitignore");
2745
+ if (existsSync11(gitignorePath)) {
2543
2746
  try {
2544
2747
  const lines = readFileSync6(gitignorePath, "utf8").split(`
2545
2748
  `);
@@ -2556,7 +2759,7 @@ class FileSearchEngine {
2556
2759
  collectFiles(dir, root, gitignoreRules, includePattern) {
2557
2760
  const results = [];
2558
2761
  try {
2559
- const stat = statSync3(dir);
2762
+ const stat = statSync4(dir);
2560
2763
  if (!stat.isDirectory()) {
2561
2764
  if (!this.isBinary(dir)) {
2562
2765
  results.push(dir);
@@ -2570,9 +2773,9 @@ class FileSearchEngine {
2570
2773
  while (queue.length > 0) {
2571
2774
  const currentDir = queue.shift();
2572
2775
  try {
2573
- const entries = readdirSync3(currentDir, { withFileTypes: true });
2776
+ const entries = readdirSync4(currentDir, { withFileTypes: true });
2574
2777
  for (const entry of entries) {
2575
- const fullPath = join4(currentDir, entry.name);
2778
+ const fullPath = join5(currentDir, entry.name);
2576
2779
  const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
2577
2780
  if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
2578
2781
  continue;
@@ -3271,8 +3474,8 @@ function createDefaultTools(options = {}) {
3271
3474
  }
3272
3475
 
3273
3476
  // src/agents/roles.ts
3274
- import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
3275
- import { resolve as resolve10, join as join5 } from "path";
3477
+ import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "fs";
3478
+ import { resolve as resolve10, join as join6 } from "path";
3276
3479
 
3277
3480
  class AgentRoleRegistry {
3278
3481
  roles = new Map;
@@ -3357,13 +3560,13 @@ class AgentRoleRegistry {
3357
3560
  }
3358
3561
  loadRolesFromDir(dirPath) {
3359
3562
  const fullPath = resolve10(dirPath);
3360
- if (!existsSync11(fullPath))
3563
+ if (!existsSync12(fullPath))
3361
3564
  return;
3362
- const entries = readdirSync4(fullPath);
3565
+ const entries = readdirSync5(fullPath);
3363
3566
  for (const entry of entries) {
3364
3567
  if (entry.endsWith(".json")) {
3365
3568
  try {
3366
- const content = readFileSync7(join5(fullPath, entry), "utf8");
3569
+ const content = readFileSync7(join6(fullPath, entry), "utf8");
3367
3570
  const parsed = JSON.parse(content);
3368
3571
  if (parsed.name && parsed.systemPrompt) {
3369
3572
  this.registerRole(parsed);
@@ -3416,7 +3619,7 @@ function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
3416
3619
  // src/agents/graph-store.ts
3417
3620
  import { Database as Database2 } from "bun:sqlite";
3418
3621
  import { resolve as resolve11 } from "path";
3419
- import { existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
3622
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
3420
3623
  class AgentGraphStore {
3421
3624
  db;
3422
3625
  constructor(dbPathOrDb) {
@@ -3426,8 +3629,8 @@ class AgentGraphStore {
3426
3629
  const dbPath = dbPathOrDb || getAgentGraphDbPath();
3427
3630
  if (dbPath !== ":memory:") {
3428
3631
  const dir = resolve11(dbPath, "..");
3429
- if (!existsSync12(dir)) {
3430
- mkdirSync6(dir, { recursive: true });
3632
+ if (!existsSync13(dir)) {
3633
+ mkdirSync7(dir, { recursive: true });
3431
3634
  }
3432
3635
  }
3433
3636
  this.db = new Database2(dbPath);
@@ -3851,8 +4054,8 @@ function registerMultiAgentTools(router2, spawner) {
3851
4054
  }
3852
4055
 
3853
4056
  // src/mcp/manager.ts
3854
- import { existsSync as existsSync13, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
3855
- import { resolve as resolve12, dirname as dirname6, join as join6 } from "path";
4057
+ import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync8 } from "fs";
4058
+ import { resolve as resolve12, dirname as dirname6, join as join7 } from "path";
3856
4059
 
3857
4060
  // src/mcp/client.ts
3858
4061
  class McpClient {
@@ -4468,7 +4671,7 @@ class McpManager {
4468
4671
  }
4469
4672
  async loadConfigFile(filePath) {
4470
4673
  const fullPath = resolve12(filePath);
4471
- if (!existsSync13(fullPath))
4674
+ if (!existsSync14(fullPath))
4472
4675
  return;
4473
4676
  this.loadedConfigFiles.add(fullPath);
4474
4677
  try {
@@ -4723,11 +4926,11 @@ class McpManager {
4723
4926
  saveServerToConfigFile(filePath, name, config) {
4724
4927
  const fullPath = resolve12(filePath);
4725
4928
  const dir = dirname6(fullPath);
4726
- if (!existsSync13(dir)) {
4727
- mkdirSync7(dir, { recursive: true });
4929
+ if (!existsSync14(dir)) {
4930
+ mkdirSync8(dir, { recursive: true });
4728
4931
  }
4729
4932
  let existing = { mcpServers: {} };
4730
- if (existsSync13(fullPath)) {
4933
+ if (existsSync14(fullPath)) {
4731
4934
  try {
4732
4935
  const content = readFileSync8(fullPath, "utf8");
4733
4936
  existing = JSON.parse(content);
@@ -4742,7 +4945,7 @@ class McpManager {
4742
4945
  }
4743
4946
  removeServerFromConfigFile(filePath, name) {
4744
4947
  const fullPath = resolve12(filePath);
4745
- if (!existsSync13(fullPath))
4948
+ if (!existsSync14(fullPath))
4746
4949
  return false;
4747
4950
  try {
4748
4951
  const content = readFileSync8(fullPath, "utf8");
@@ -4772,11 +4975,11 @@ class McpManager {
4772
4975
  }
4773
4976
  }
4774
4977
  getDefaultConfigFile(cwd = process.cwd()) {
4775
- const workspaceConfig = join6(cwd, ".mcp.json");
4776
- if (existsSync13(workspaceConfig))
4978
+ const workspaceConfig = join7(cwd, ".mcp.json");
4979
+ if (existsSync14(workspaceConfig))
4777
4980
  return workspaceConfig;
4778
- const altConfig = join6(cwd, "mcp_config.json");
4779
- if (existsSync13(altConfig))
4981
+ const altConfig = join7(cwd, "mcp_config.json");
4982
+ if (existsSync14(altConfig))
4780
4983
  return altConfig;
4781
4984
  return workspaceConfig;
4782
4985
  }
@@ -4797,7 +5000,7 @@ class McpManager {
4797
5000
 
4798
5001
  // src/storage/sqlite-store.ts
4799
5002
  import { Database as Database3 } from "bun:sqlite";
4800
- import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
5003
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
4801
5004
  import { dirname as dirname7 } from "path";
4802
5005
  class SqliteThreadStore {
4803
5006
  db;
@@ -4805,8 +5008,8 @@ class SqliteThreadStore {
4805
5008
  const effectivePath = dbPath || this.getDefaultDbPath();
4806
5009
  if (effectivePath !== ":memory:") {
4807
5010
  const dir = dirname7(effectivePath);
4808
- if (!existsSync14(dir)) {
4809
- mkdirSync8(dir, { recursive: true });
5011
+ if (!existsSync15(dir)) {
5012
+ mkdirSync9(dir, { recursive: true });
4810
5013
  }
4811
5014
  }
4812
5015
  this.db = new Database3(effectivePath);
@@ -5040,8 +5243,8 @@ class SessionPersistenceManager {
5040
5243
  }
5041
5244
 
5042
5245
  // src/skills/loader.ts
5043
- import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
5044
- import { resolve as resolve13, join as join7 } from "path";
5246
+ import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
5247
+ import { resolve as resolve13, join as join8 } from "path";
5045
5248
  import { homedir as homedir3 } from "os";
5046
5249
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
5047
5250
 
@@ -5112,7 +5315,7 @@ class SkillsLoader {
5112
5315
  resolve13(cwd, "skills")
5113
5316
  ];
5114
5317
  for (const cand of candidates) {
5115
- if (existsSync15(cand) && !roots.includes(cand)) {
5318
+ if (existsSync16(cand) && !roots.includes(cand)) {
5116
5319
  roots.push(cand);
5117
5320
  }
5118
5321
  }
@@ -5121,7 +5324,7 @@ class SkillsLoader {
5121
5324
  roots.push(getGlobalSkillsDir(), resolve13(homedir3(), ".gemini", "config", "skills"));
5122
5325
  }
5123
5326
  roots.push(...this.customRoots.map((r) => resolve13(r)));
5124
- return roots.filter((r) => existsSync15(r));
5327
+ return roots.filter((r) => existsSync16(r));
5125
5328
  }
5126
5329
  discoverSkills(cwd, options) {
5127
5330
  return this.listSkills(cwd, options);
@@ -5137,12 +5340,12 @@ class SkillsLoader {
5137
5340
  const discovered = new Map;
5138
5341
  for (const root of roots) {
5139
5342
  try {
5140
- const entries = readdirSync5(root, { withFileTypes: true });
5343
+ const entries = readdirSync6(root, { withFileTypes: true });
5141
5344
  for (const entry of entries) {
5142
5345
  if (entry.isDirectory()) {
5143
- const skillDir = join7(root, entry.name);
5144
- const skillFilePath = join7(skillDir, "SKILL.md");
5145
- if (existsSync15(skillFilePath)) {
5346
+ const skillDir = join8(root, entry.name);
5347
+ const skillFilePath = join8(skillDir, "SKILL.md");
5348
+ if (existsSync16(skillFilePath)) {
5146
5349
  const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
5147
5350
  if (meta && !discovered.has(meta.name)) {
5148
5351
  meta.enabled = !this.isSkillDisabled(meta.name);
@@ -5155,7 +5358,7 @@ class SkillsLoader {
5155
5358
  }
5156
5359
  } catch {}
5157
5360
  }
5158
- const result = Array.from(discovered.values());
5361
+ const result = Array.from(discovered.values()).sort((a, b) => a.name.localeCompare(b.name));
5159
5362
  this.skillsCache.set(cacheKey, { timestamp: now, skills: result });
5160
5363
  return result;
5161
5364
  }
@@ -5244,10 +5447,10 @@ class SkillsLoader {
5244
5447
  const skills = this.listSkills(cwd, { includeDisabled: false });
5245
5448
  if (skills.length === 0)
5246
5449
  return "";
5247
- const workspaceSkills = skills.filter((s) => s.scope === "workspace");
5248
- const builtInSkills = skills.filter((s) => s.scope === "built-in");
5249
- const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in");
5250
- const selectedSkills = [...workspaceSkills, ...builtInSkills, ...otherSkills].slice(0, 150);
5450
+ const workspaceSkills = skills.filter((s) => s.scope === "workspace").sort((a, b) => a.name.localeCompare(b.name));
5451
+ const builtInSkills = skills.filter((s) => s.scope === "built-in").sort((a, b) => a.name.localeCompare(b.name));
5452
+ const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in").sort((a, b) => a.name.localeCompare(b.name));
5453
+ const selectedSkills = [...workspaceSkills, ...builtInSkills, ...otherSkills].slice(0, 300);
5251
5454
  const lines = selectedSkills.map((s) => {
5252
5455
  const desc = s.shortDescription || s.description;
5253
5456
  return `- **${s.name}**: ${desc}`;
@@ -5265,8 +5468,8 @@ When tackling complex specialized tasks that match any of these skills, autonomo
5265
5468
  }
5266
5469
 
5267
5470
  // src/memories/store.ts
5268
- import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync9, readdirSync as readdirSync6 } from "fs";
5269
- import { resolve as resolve14, join as join8, basename, dirname as dirname8 } from "path";
5471
+ import { existsSync as existsSync17, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync10, readdirSync as readdirSync7 } from "fs";
5472
+ import { resolve as resolve14, join as join9, basename, dirname as dirname8 } from "path";
5270
5473
  import { createHash } from "crypto";
5271
5474
  class MemoryStore {
5272
5475
  globalPath;
@@ -5278,7 +5481,7 @@ class MemoryStore {
5278
5481
  findProjectRoot(cwd) {
5279
5482
  let current = resolve14(cwd);
5280
5483
  while (true) {
5281
- if (existsSync16(join8(current, ".git"))) {
5484
+ if (existsSync17(join9(current, ".git"))) {
5282
5485
  return current;
5283
5486
  }
5284
5487
  const parent = dirname8(current);
@@ -5297,24 +5500,24 @@ class MemoryStore {
5297
5500
  getProjectMemoryDir(cwd) {
5298
5501
  if (this.customWorkspacePath) {
5299
5502
  const dir2 = resolve14(this.customWorkspacePath);
5300
- if (!existsSync16(dir2)) {
5503
+ if (!existsSync17(dir2)) {
5301
5504
  try {
5302
- mkdirSync9(dir2, { recursive: true });
5505
+ mkdirSync10(dir2, { recursive: true });
5303
5506
  } catch {}
5304
5507
  }
5305
5508
  return dir2;
5306
5509
  }
5307
5510
  const slug = this.getProjectSlug(cwd);
5308
- const dir = join8(getProjectsDir(), slug, "memory");
5309
- if (!existsSync16(dir)) {
5511
+ const dir = join9(getProjectsDir(), slug, "memory");
5512
+ if (!existsSync17(dir)) {
5310
5513
  try {
5311
- mkdirSync9(dir, { recursive: true });
5514
+ mkdirSync10(dir, { recursive: true });
5312
5515
  } catch {}
5313
5516
  }
5314
5517
  return dir;
5315
5518
  }
5316
5519
  getMemoryIndexPath(cwd) {
5317
- return join8(this.getProjectMemoryDir(cwd), "MEMORY.md");
5520
+ return join9(this.getProjectMemoryDir(cwd), "MEMORY.md");
5318
5521
  }
5319
5522
  normalizeCategory(raw) {
5320
5523
  const cat = raw.toLowerCase().trim();
@@ -5333,7 +5536,7 @@ class MemoryStore {
5333
5536
  const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
5334
5537
  const memoryDir = this.getProjectMemoryDir(params.cwd);
5335
5538
  const fileName = `${type}_${sanitizedName}.md`;
5336
- const filePath = join8(memoryDir, fileName);
5539
+ const filePath = join9(memoryDir, fileName);
5337
5540
  const nowIso = new Date().toISOString();
5338
5541
  const cleanContent = params.content.trim();
5339
5542
  const desc = (params.description || cleanContent.split(`
@@ -5368,17 +5571,17 @@ class MemoryStore {
5368
5571
  }
5369
5572
  readTopicMemory(topicNameOrFile, cwd) {
5370
5573
  const memoryDir = this.getProjectMemoryDir(cwd);
5371
- let targetPath = join8(memoryDir, topicNameOrFile);
5372
- if (!existsSync16(targetPath)) {
5574
+ let targetPath = join9(memoryDir, topicNameOrFile);
5575
+ if (!existsSync17(targetPath)) {
5373
5576
  if (!topicNameOrFile.endsWith(".md")) {
5374
- targetPath = join8(memoryDir, `${topicNameOrFile}.md`);
5577
+ targetPath = join9(memoryDir, `${topicNameOrFile}.md`);
5375
5578
  }
5376
5579
  }
5377
- if (!existsSync16(targetPath)) {
5378
- const files = readdirSync6(memoryDir);
5580
+ if (!existsSync17(targetPath)) {
5581
+ const files = readdirSync7(memoryDir);
5379
5582
  const match = files.find((f) => f.includes(topicNameOrFile));
5380
5583
  if (match) {
5381
- targetPath = join8(memoryDir, match);
5584
+ targetPath = join9(memoryDir, match);
5382
5585
  } else {
5383
5586
  return null;
5384
5587
  }
@@ -5439,12 +5642,12 @@ class MemoryStore {
5439
5642
  }
5440
5643
  syncMemoryIndex(cwd) {
5441
5644
  const memoryDir = this.getProjectMemoryDir(cwd);
5442
- const indexPath = join8(memoryDir, "MEMORY.md");
5443
- const files = existsSync16(memoryDir) ? readdirSync6(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
5645
+ const indexPath = join9(memoryDir, "MEMORY.md");
5646
+ const files = existsSync17(memoryDir) ? readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
5444
5647
  const items = [];
5445
5648
  for (const f of files) {
5446
5649
  try {
5447
- const full = join8(memoryDir, f);
5650
+ const full = join9(memoryDir, f);
5448
5651
  const parsed = this.parseTopicFile(readFileSync10(full, "utf8"), full);
5449
5652
  items.push({
5450
5653
  type: parsed.type,
@@ -5471,7 +5674,7 @@ class MemoryStore {
5471
5674
  }
5472
5675
  loadMemoryIndex(cwd) {
5473
5676
  const indexPath = this.getMemoryIndexPath(cwd);
5474
- if (!existsSync16(indexPath))
5677
+ if (!existsSync17(indexPath))
5475
5678
  return "";
5476
5679
  try {
5477
5680
  const raw = readFileSync10(indexPath, "utf8");
@@ -5487,13 +5690,13 @@ class MemoryStore {
5487
5690
  }
5488
5691
  listProjectMemories(cwd) {
5489
5692
  const memoryDir = this.getProjectMemoryDir(cwd);
5490
- if (!existsSync16(memoryDir))
5693
+ if (!existsSync17(memoryDir))
5491
5694
  return [];
5492
- const files = readdirSync6(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
5695
+ const files = readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
5493
5696
  const list = [];
5494
5697
  for (const f of files) {
5495
5698
  try {
5496
- const full = join8(memoryDir, f);
5699
+ const full = join9(memoryDir, f);
5497
5700
  list.push(this.parseTopicFile(readFileSync10(full, "utf8"), full));
5498
5701
  } catch {}
5499
5702
  }
@@ -5545,8 +5748,8 @@ class MemoryStore {
5545
5748
  }
5546
5749
 
5547
5750
  // src/worktree/manager.ts
5548
- import { resolve as resolve16, join as join9 } from "path";
5549
- import { existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6, readFileSync as readFileSync11 } from "fs";
5751
+ import { resolve as resolve16, join as join10 } from "path";
5752
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6, readFileSync as readFileSync11 } from "fs";
5550
5753
 
5551
5754
  // src/worktree/git.ts
5552
5755
  import { resolve as resolve15 } from "path";
@@ -5696,15 +5899,15 @@ class WorktreeManager {
5696
5899
  const branchName = options.branch || `groupy/${taskId}`;
5697
5900
  const targetDir = options.worktreePath || (this.baseStorageDir ? resolve16(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve16(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
5698
5901
  const worktreeParent = resolve16(targetDir, "..");
5699
- if (!existsSync17(worktreeParent)) {
5700
- mkdirSync10(worktreeParent, { recursive: true });
5902
+ if (!existsSync18(worktreeParent)) {
5903
+ mkdirSync11(worktreeParent, { recursive: true });
5701
5904
  }
5702
5905
  const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
5703
5906
  const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
5704
5907
  if (!result.success) {
5705
5908
  throw new Error(`Failed to create git worktree: ${result.error}`);
5706
5909
  }
5707
- const metaPath = join9(targetDir, "groupy-thread.json");
5910
+ const metaPath = join10(targetDir, "groupy-thread.json");
5708
5911
  try {
5709
5912
  writeFileSync6(metaPath, JSON.stringify({
5710
5913
  version: 1,
@@ -5730,8 +5933,8 @@ class WorktreeManager {
5730
5933
  return [];
5731
5934
  const worktrees = await listWorktreesGit(repoRoot);
5732
5935
  return worktrees.map((wt) => {
5733
- const metaPath = join9(wt.path, "groupy-thread.json");
5734
- if (existsSync17(metaPath)) {
5936
+ const metaPath = join10(wt.path, "groupy-thread.json");
5937
+ if (existsSync18(metaPath)) {
5735
5938
  try {
5736
5939
  const raw = JSON.parse(readFileSync11(metaPath, "utf8"));
5737
5940
  return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
@@ -6429,7 +6632,7 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
6429
6632
  // package.json
6430
6633
  var package_default = {
6431
6634
  name: "@pikaa-ai/pikaa",
6432
- version: "0.3.18",
6635
+ version: "0.3.20",
6433
6636
  description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
6434
6637
  main: "./dist/index.js",
6435
6638
  module: "./dist/index.js",
@@ -6804,7 +7007,12 @@ ${preview}${more}`);
6804
7007
  if (metrics.inputTokens !== undefined || metrics.outputTokens !== undefined) {
6805
7008
  const inStr = metrics.inputTokens !== undefined ? formatTokens(metrics.inputTokens) : "0";
6806
7009
  const outStr = metrics.outputTokens !== undefined ? formatTokens(metrics.outputTokens) : "0";
6807
- parts.push(`${style.cyan(`${inStr} in`)} ${style.dim("/")} ${style.cyan(`${outStr} out`)}`);
7010
+ if (metrics.cachedTokens !== undefined && metrics.cachedTokens > 0) {
7011
+ const cachedStr = formatTokens(metrics.cachedTokens);
7012
+ parts.push(`${style.cyan(`${inStr} in`)} ${style.dim(`(${cachedStr} cached)`)} ${style.dim("/")} ${style.cyan(`${outStr} out`)}`);
7013
+ } else {
7014
+ parts.push(`${style.cyan(`${inStr} in`)} ${style.dim("/")} ${style.cyan(`${outStr} out`)}`);
7015
+ }
6808
7016
  } else if (metrics.totalTokens !== undefined && metrics.totalTokens > 0) {
6809
7017
  parts.push(`${style.dim(`${formatTokens(metrics.totalTokens)} tokens`)}`);
6810
7018
  }
@@ -7921,8 +8129,8 @@ async function promptInteractiveList(config) {
7921
8129
  }
7922
8130
 
7923
8131
  // src/security/scanner.ts
7924
- import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
7925
- import { join as join10, relative as relative2, resolve as resolve17 } from "path";
8132
+ import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
8133
+ import { join as join11, relative as relative2, resolve as resolve17 } from "path";
7926
8134
  var SECURITY_RULES = [
7927
8135
  {
7928
8136
  id: "SEC-001",
@@ -8060,21 +8268,21 @@ async function runSecurityScan(targetDir, options = {}) {
8060
8268
  const findings = [];
8061
8269
  let scannedCount = 0;
8062
8270
  function walk(current) {
8063
- if (scannedCount >= maxFiles || !existsSync18(current))
8271
+ if (scannedCount >= maxFiles || !existsSync19(current))
8064
8272
  return;
8065
8273
  let entries;
8066
8274
  try {
8067
- entries = readdirSync7(current);
8275
+ entries = readdirSync8(current);
8068
8276
  } catch {
8069
8277
  return;
8070
8278
  }
8071
8279
  for (const entry of entries) {
8072
8280
  if (scannedCount >= maxFiles)
8073
8281
  break;
8074
- const fullPath = join10(current, entry);
8282
+ const fullPath = join11(current, entry);
8075
8283
  let stat;
8076
8284
  try {
8077
- stat = statSync4(fullPath);
8285
+ stat = statSync5(fullPath);
8078
8286
  } catch {
8079
8287
  continue;
8080
8288
  }
@@ -8179,8 +8387,8 @@ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
8179
8387
  var SQLITE_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
8180
8388
 
8181
8389
  // src/init/project-analyzer.ts
8182
- import { existsSync as existsSync19, readFileSync as readFileSync13, readdirSync as readdirSync8 } from "fs";
8183
- import { join as join11, basename as basename3 } from "path";
8390
+ import { existsSync as existsSync20, readFileSync as readFileSync13, readdirSync as readdirSync9 } from "fs";
8391
+ import { join as join12, basename as basename3 } from "path";
8184
8392
 
8185
8393
  class ProjectAnalyzer {
8186
8394
  cwd;
@@ -8198,8 +8406,8 @@ class ProjectAnalyzer {
8198
8406
  const architectureNotes = [];
8199
8407
  const codeConventions = [];
8200
8408
  let description = readmeInfo.description;
8201
- const pkgPath = join11(this.cwd, "package.json");
8202
- if (existsSync19(pkgPath)) {
8409
+ const pkgPath = join12(this.cwd, "package.json");
8410
+ if (existsSync20(pkgPath)) {
8203
8411
  try {
8204
8412
  const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
8205
8413
  if (!description && pkg.description)
@@ -8272,8 +8480,8 @@ class ProjectAnalyzer {
8272
8480
  }
8273
8481
  } catch {}
8274
8482
  }
8275
- const tsconfigPath = join11(this.cwd, "tsconfig.json");
8276
- if (existsSync19(tsconfigPath)) {
8483
+ const tsconfigPath = join12(this.cwd, "tsconfig.json");
8484
+ if (existsSync20(tsconfigPath)) {
8277
8485
  try {
8278
8486
  const tsconfig = JSON.parse(readFileSync13(tsconfigPath, "utf8"));
8279
8487
  if (tsconfig.compilerOptions?.strict) {
@@ -8284,8 +8492,8 @@ class ProjectAnalyzer {
8284
8492
  }
8285
8493
  } catch {}
8286
8494
  }
8287
- const cargoPath = join11(this.cwd, "Cargo.toml");
8288
- if (existsSync19(cargoPath)) {
8495
+ const cargoPath = join12(this.cwd, "Cargo.toml");
8496
+ if (existsSync20(cargoPath)) {
8289
8497
  try {
8290
8498
  commands.dev = commands.dev || "cargo run";
8291
8499
  commands.build = commands.build || "cargo build";
@@ -8294,8 +8502,8 @@ class ProjectAnalyzer {
8294
8502
  frameworks.push("Rust Cargo");
8295
8503
  } catch {}
8296
8504
  }
8297
- const goModPath = join11(this.cwd, "go.mod");
8298
- if (existsSync19(goModPath)) {
8505
+ const goModPath = join12(this.cwd, "go.mod");
8506
+ if (existsSync20(goModPath)) {
8299
8507
  try {
8300
8508
  commands.dev = commands.dev || "go run .";
8301
8509
  commands.build = commands.build || "go build ./...";
@@ -8304,37 +8512,37 @@ class ProjectAnalyzer {
8304
8512
  frameworks.push("Go Modules");
8305
8513
  } catch {}
8306
8514
  }
8307
- const pyprojectPath = join11(this.cwd, "pyproject.toml");
8308
- const requirementsPath = join11(this.cwd, "requirements.txt");
8309
- if (existsSync19(pyprojectPath) || existsSync19(requirementsPath)) {
8515
+ const pyprojectPath = join12(this.cwd, "pyproject.toml");
8516
+ const requirementsPath = join12(this.cwd, "requirements.txt");
8517
+ if (existsSync20(pyprojectPath) || existsSync20(requirementsPath)) {
8310
8518
  commands.test = commands.test || "pytest";
8311
8519
  commands.lint = commands.lint || "ruff check .";
8312
- if (existsSync19(join11(this.cwd, "uv.lock"))) {
8520
+ if (existsSync20(join12(this.cwd, "uv.lock"))) {
8313
8521
  frameworks.push("uv");
8314
8522
  commands.test = "uv run pytest";
8315
- } else if (existsSync19(join11(this.cwd, "poetry.lock"))) {
8523
+ } else if (existsSync20(join12(this.cwd, "poetry.lock"))) {
8316
8524
  frameworks.push("Poetry");
8317
8525
  commands.test = "poetry run pytest";
8318
8526
  }
8319
8527
  }
8320
- if (existsSync19(join11(this.cwd, "Dockerfile"))) {
8528
+ if (existsSync20(join12(this.cwd, "Dockerfile"))) {
8321
8529
  infrastructure.push("Docker");
8322
8530
  const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
8323
8531
  commands.dockerBuild = `docker build -t ${sanitizedName || "app"} .`;
8324
8532
  }
8325
- if (existsSync19(join11(this.cwd, "nginx.conf"))) {
8533
+ if (existsSync20(join12(this.cwd, "nginx.conf"))) {
8326
8534
  infrastructure.push("Nginx");
8327
8535
  }
8328
- if (existsSync19(join11(this.cwd, "src/api.ts")) || existsSync19(join11(this.cwd, "src/api"))) {
8536
+ if (existsSync20(join12(this.cwd, "src/api.ts")) || existsSync20(join12(this.cwd, "src/api"))) {
8329
8537
  architectureNotes.push("Backend API endpoints and network client logic are centralized in `src/api`.");
8330
8538
  }
8331
- if (existsSync19(join11(this.cwd, "src/components"))) {
8539
+ if (existsSync20(join12(this.cwd, "src/components"))) {
8332
8540
  architectureNotes.push("Reusable UI presentation components live in `src/components/`.");
8333
8541
  }
8334
- if (existsSync19(join11(this.cwd, "src/types.ts")) || existsSync19(join11(this.cwd, "src/types"))) {
8542
+ if (existsSync20(join12(this.cwd, "src/types.ts")) || existsSync20(join12(this.cwd, "src/types"))) {
8335
8543
  architectureNotes.push("Shared TypeScript data models and interfaces are defined in `src/types`.");
8336
8544
  }
8337
- if (existsSync19(join11(this.cwd, ".env.example"))) {
8545
+ if (existsSync20(join12(this.cwd, ".env.example"))) {
8338
8546
  architectureNotes.push("Environment configuration template is in `.env.example`.");
8339
8547
  }
8340
8548
  if (commands.typecheck || commands.lint || commands.test) {
@@ -8351,7 +8559,7 @@ class ProjectAnalyzer {
8351
8559
  let hasExistingInstructions = false;
8352
8560
  let existingInstructionFile;
8353
8561
  for (const f of instructionFiles) {
8354
- if (existsSync19(join11(this.cwd, f))) {
8562
+ if (existsSync20(join12(this.cwd, f))) {
8355
8563
  hasExistingInstructions = true;
8356
8564
  existingInstructionFile = f;
8357
8565
  break;
@@ -8432,8 +8640,8 @@ class ProjectAnalyzer {
8432
8640
  extractReadmeMetadata() {
8433
8641
  const readmeFiles = ["README.md", "readme.md", "README.MD"];
8434
8642
  for (const file of readmeFiles) {
8435
- const fullPath = join11(this.cwd, file);
8436
- if (existsSync19(fullPath)) {
8643
+ const fullPath = join12(this.cwd, file);
8644
+ if (existsSync20(fullPath)) {
8437
8645
  try {
8438
8646
  const content = readFileSync13(fullPath, "utf8");
8439
8647
  const lines = content.split(`
@@ -8458,8 +8666,8 @@ class ProjectAnalyzer {
8458
8666
  return {};
8459
8667
  }
8460
8668
  detectProjectName() {
8461
- const pkgPath = join11(this.cwd, "package.json");
8462
- if (existsSync19(pkgPath)) {
8669
+ const pkgPath = join12(this.cwd, "package.json");
8670
+ if (existsSync20(pkgPath)) {
8463
8671
  try {
8464
8672
  const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
8465
8673
  if (pkg.name && pkg.name !== "frontend" && pkg.name !== "backend" && pkg.name !== "app") {
@@ -8467,16 +8675,16 @@ class ProjectAnalyzer {
8467
8675
  }
8468
8676
  } catch {}
8469
8677
  }
8470
- const cargoPath = join11(this.cwd, "Cargo.toml");
8471
- if (existsSync19(cargoPath)) {
8678
+ const cargoPath = join12(this.cwd, "Cargo.toml");
8679
+ if (existsSync20(cargoPath)) {
8472
8680
  try {
8473
8681
  const match = readFileSync13(cargoPath, "utf8").match(/name\s*=\s*"([^"]+)"/);
8474
8682
  if (match?.[1])
8475
8683
  return match[1];
8476
8684
  } catch {}
8477
8685
  }
8478
- const goModPath = join11(this.cwd, "go.mod");
8479
- if (existsSync19(goModPath)) {
8686
+ const goModPath = join12(this.cwd, "go.mod");
8687
+ if (existsSync20(goModPath)) {
8480
8688
  try {
8481
8689
  const match = readFileSync13(goModPath, "utf8").match(/module\s+([^\s]+)/);
8482
8690
  if (match?.[1])
@@ -8487,53 +8695,53 @@ class ProjectAnalyzer {
8487
8695
  }
8488
8696
  detectLanguages() {
8489
8697
  const langs = new Set;
8490
- if (existsSync19(join11(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
8698
+ if (existsSync20(join12(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
8491
8699
  langs.add("TypeScript");
8492
8700
  }
8493
- if (existsSync19(join11(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
8701
+ if (existsSync20(join12(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
8494
8702
  langs.add("JavaScript");
8495
8703
  }
8496
- if (existsSync19(join11(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
8704
+ if (existsSync20(join12(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
8497
8705
  langs.add("Rust");
8498
8706
  }
8499
- if (existsSync19(join11(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
8707
+ if (existsSync20(join12(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
8500
8708
  langs.add("Go");
8501
8709
  }
8502
- if (existsSync19(join11(this.cwd, "pyproject.toml")) || existsSync19(join11(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
8710
+ if (existsSync20(join12(this.cwd, "pyproject.toml")) || existsSync20(join12(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
8503
8711
  langs.add("Python");
8504
8712
  }
8505
- if (existsSync19(join11(this.cwd, "pom.xml")) || existsSync19(join11(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
8713
+ if (existsSync20(join12(this.cwd, "pom.xml")) || existsSync20(join12(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
8506
8714
  langs.add("Java");
8507
8715
  }
8508
- if (existsSync19(join11(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
8716
+ if (existsSync20(join12(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
8509
8717
  langs.add("C/C++");
8510
8718
  }
8511
8719
  return Array.from(langs);
8512
8720
  }
8513
8721
  detectPackageManager() {
8514
- if (existsSync19(join11(this.cwd, "bun.lockb")) || existsSync19(join11(this.cwd, "bun.lock")))
8722
+ if (existsSync20(join12(this.cwd, "bun.lockb")) || existsSync20(join12(this.cwd, "bun.lock")))
8515
8723
  return "bun";
8516
- if (existsSync19(join11(this.cwd, "pnpm-lock.yaml")))
8724
+ if (existsSync20(join12(this.cwd, "pnpm-lock.yaml")))
8517
8725
  return "pnpm";
8518
- if (existsSync19(join11(this.cwd, "yarn.lock")))
8726
+ if (existsSync20(join12(this.cwd, "yarn.lock")))
8519
8727
  return "yarn";
8520
- if (existsSync19(join11(this.cwd, "package-lock.json")))
8728
+ if (existsSync20(join12(this.cwd, "package-lock.json")))
8521
8729
  return "npm";
8522
- if (existsSync19(join11(this.cwd, "Cargo.lock")) || existsSync19(join11(this.cwd, "Cargo.toml")))
8730
+ if (existsSync20(join12(this.cwd, "Cargo.lock")) || existsSync20(join12(this.cwd, "Cargo.toml")))
8523
8731
  return "cargo";
8524
- if (existsSync19(join11(this.cwd, "uv.lock")))
8732
+ if (existsSync20(join12(this.cwd, "uv.lock")))
8525
8733
  return "uv";
8526
- if (existsSync19(join11(this.cwd, "poetry.lock")))
8734
+ if (existsSync20(join12(this.cwd, "poetry.lock")))
8527
8735
  return "poetry";
8528
- if (existsSync19(join11(this.cwd, "go.sum")) || existsSync19(join11(this.cwd, "go.mod")))
8736
+ if (existsSync20(join12(this.cwd, "go.sum")) || existsSync20(join12(this.cwd, "go.mod")))
8529
8737
  return "go";
8530
- if (existsSync19(join11(this.cwd, "package.json")))
8738
+ if (existsSync20(join12(this.cwd, "package.json")))
8531
8739
  return "npm";
8532
8740
  return;
8533
8741
  }
8534
8742
  hasFileWithExtension(...exts) {
8535
8743
  try {
8536
- const entries = readdirSync8(this.cwd);
8744
+ const entries = readdirSync9(this.cwd);
8537
8745
  return entries.some((e) => exts.some((ext) => e.endsWith(ext)));
8538
8746
  } catch {
8539
8747
  return false;
@@ -8541,16 +8749,16 @@ class ProjectAnalyzer {
8541
8749
  }
8542
8750
  }
8543
8751
  // src/init/init-command.ts
8544
- import { existsSync as existsSync20, writeFileSync as writeFileSync7 } from "fs";
8545
- import { join as join12 } from "path";
8752
+ import { existsSync as existsSync21, writeFileSync as writeFileSync7 } from "fs";
8753
+ import { join as join13 } from "path";
8546
8754
  function runProjectInit(options = {}) {
8547
8755
  const cwd = options.cwd || process.cwd();
8548
8756
  const filename = options.filename || "AGENTS.md";
8549
- const targetPath = join12(cwd, filename);
8757
+ const targetPath = join13(cwd, filename);
8550
8758
  const analyzer = new ProjectAnalyzer(cwd);
8551
8759
  const analysis = analyzer.analyze();
8552
8760
  const content = analyzer.generateAgentsMarkdown(analysis);
8553
- const alreadyExists = existsSync20(targetPath);
8761
+ const alreadyExists = existsSync21(targetPath);
8554
8762
  writeFileSync7(targetPath, content, "utf8");
8555
8763
  return {
8556
8764
  success: true,
@@ -9324,7 +9532,7 @@ function printSessionStats(ctx) {
9324
9532
  const turns = ctx.repl?.turnCount ?? 0;
9325
9533
  const history = ctx.session.getHistory();
9326
9534
  const historyTokens = estimateTotalTokens(history);
9327
- const maxTokens = 128000;
9535
+ const maxTokens = DEFAULT_MAX_CONTEXT_TOKENS;
9328
9536
  const contextPct = Math.round(historyTokens / maxTokens * 100);
9329
9537
  const colorFn = contextPct < 50 ? style.green : contextPct < 80 ? style.yellow : style.red;
9330
9538
  const agents = ctx.spawner?.listAgents() || [];
@@ -10015,9 +10223,9 @@ class MarkdownHighlighter {
10015
10223
  }
10016
10224
 
10017
10225
  // src/cli/update-checker.ts
10018
- import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
10226
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
10019
10227
  import { homedir as homedir5 } from "os";
10020
- import { join as join13 } from "path";
10228
+ import { join as join14 } from "path";
10021
10229
  var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
10022
10230
  function parseSemver(v) {
10023
10231
  const clean = v.replace(/^v/, "").trim();
@@ -10038,8 +10246,8 @@ function isNewerVersion(current, remote) {
10038
10246
  return remPatch > curPatch;
10039
10247
  }
10040
10248
  function getUpdateCachePath() {
10041
- const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join13(homedir5(), ".pikaa");
10042
- return join13(baseDir, "update-cache.json");
10249
+ const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join14(homedir5(), ".pikaa");
10250
+ return join14(baseDir, "update-cache.json");
10043
10251
  }
10044
10252
  async function fetchLatestNpmVersion(packageName, timeoutMs = 1500) {
10045
10253
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
@@ -10072,7 +10280,7 @@ async function checkForUpdates(options = {}) {
10072
10280
  const cachePath = options.cachePath || getUpdateCachePath();
10073
10281
  const now = Date.now();
10074
10282
  let cached = null;
10075
- if (!options.force && existsSync21(cachePath)) {
10283
+ if (!options.force && existsSync22(cachePath)) {
10076
10284
  try {
10077
10285
  const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
10078
10286
  if (raw && typeof raw.lastChecked === "number" && typeof raw.latestVersion === "string") {
@@ -10102,9 +10310,9 @@ async function checkForUpdates(options = {}) {
10102
10310
  return null;
10103
10311
  }
10104
10312
  try {
10105
- const parentDir = join13(cachePath, "..");
10106
- if (!existsSync21(parentDir)) {
10107
- mkdirSync11(parentDir, { recursive: true });
10313
+ const parentDir = join14(cachePath, "..");
10314
+ if (!existsSync22(parentDir)) {
10315
+ mkdirSync12(parentDir, { recursive: true });
10108
10316
  }
10109
10317
  const cacheData = {
10110
10318
  lastChecked: now,
@@ -10268,6 +10476,7 @@ class CliRepl {
10268
10476
  inputTokens: msg.inputTokens,
10269
10477
  outputTokens: msg.outputTokens || (this.turnCharsOut > 0 ? Math.round(this.turnCharsOut / 3.8) : undefined),
10270
10478
  totalTokens: msg.totalTokens,
10479
+ cachedTokens: msg.cachedTokens,
10271
10480
  contextTokens: msg.contextTokens,
10272
10481
  maxContextTokens: msg.maxContextTokens,
10273
10482
  sessionUptimeMs,
@@ -10526,7 +10735,7 @@ async function main() {
10526
10735
  resolve21(cwd, "mcp_config.json")
10527
10736
  ].filter(Boolean);
10528
10737
  for (const cfg of candidateConfigs) {
10529
- if (existsSync22(cfg)) {
10738
+ if (existsSync23(cfg)) {
10530
10739
  try {
10531
10740
  await mcpManager.loadConfigFile(cfg);
10532
10741
  mcpManager.registerToolsIntoRouter(tools4);