@pikaa-ai/pikaa 0.3.27 → 0.3.28

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 resolve22 } from "path";
6
- import { existsSync as existsSync24 } from "fs";
6
+ import { existsSync as existsSync25 } from "fs";
7
7
  import { createInterface } from "readline";
8
8
 
9
9
  // src/auth/store.ts
@@ -1109,761 +1109,1335 @@ class EphemeralWorkspaceManager {
1109
1109
  }
1110
1110
  }
1111
1111
  var globalEphemeralWorkspace = new EphemeralWorkspaceManager;
1112
+ // src/verification/verifier.ts
1113
+ import { spawnSync } from "child_process";
1114
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
1115
+ import { join as join6 } from "path";
1112
1116
 
1113
- // src/session/turn.ts
1114
- async function runTurn(session, turnContext, input) {
1115
- const { turnId, signal } = turnContext;
1116
- session.emitEvent({
1117
- type: "TurnStarted",
1118
- turnId
1119
- });
1120
- const currentHistory = session.getHistory();
1121
- const estimatedTokens = estimateTotalTokens(currentHistory);
1122
- const maxTokenLimit = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS;
1123
- if (estimatedTokens > maxTokenLimit) {
1124
- const compacted = compactHistory(currentHistory);
1125
- session.setHistory(compacted);
1126
- session.emitEvent({
1127
- type: "Warning",
1128
- message: `Auto-compacted conversation history (${estimatedTokens} estimated tokens exceeded limit).`
1129
- });
1117
+ // src/init/project-analyzer.ts
1118
+ import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
1119
+ import { join as join5, basename } from "path";
1120
+
1121
+ class ProjectAnalyzer {
1122
+ cwd;
1123
+ constructor(cwd = process.cwd()) {
1124
+ this.cwd = cwd;
1130
1125
  }
1131
- const userItem = {
1132
- id: `msg_user_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1133
- type: "user_message",
1134
- content: typeof input === "string" ? input : input?.text ?? input?.prompt ?? "",
1135
- images: typeof input === "object" ? input?.images : undefined,
1136
- createdAt: Date.now()
1137
- };
1138
- session.addHistoryItem(userItem);
1139
- session.emitEvent({
1140
- type: "ItemCompleted",
1141
- turnId,
1142
- item: userItem
1143
- });
1144
- const cwd = turnContext.environment.cwd;
1145
- const worldState = await captureWorldState(cwd);
1146
- const memoriesPrompt = session.memoryStore?.formatMemoriesPrompt(cwd);
1147
- const skillsPrompt = session.skillsLoader?.formatSkillsPrompt(cwd);
1148
- const mcpPrompt = session.mcpManager?.formatMcpPrompt();
1149
- const effectiveSystemPrompt = buildSystemPrompt({
1150
- basePrompt: session.systemPrompt || undefined,
1151
- worldStatePrompt: formatWorldStatePrompt(worldState),
1152
- memoriesPrompt,
1153
- skillsPrompt,
1154
- mcpPrompt,
1155
- isOrchestrator: session.tools.has("spawn_agent"),
1156
- cwd
1157
- });
1158
- let iteration = 0;
1159
- let accumulatedInputTokens = 0;
1160
- let accumulatedOutputTokens = 0;
1161
- let accumulatedCachedTokens = 0;
1162
- const clientSession = session.modelClient.newSession();
1163
- try {
1164
- while (iteration < turnContext.maxIterations) {
1165
- if (signal.aborted) {
1166
- throw new TurnAbortedError;
1167
- }
1168
- iteration++;
1169
- let currentAgentText = "";
1170
- const toolCallRequests = [];
1171
- let iterInputTokens = Math.ceil((effectiveSystemPrompt.length + JSON.stringify(session.getHistory()).length) / 4);
1172
- let iterOutputTokens = 0;
1173
- const stream = clientSession.stream({
1174
- model: turnContext.model,
1175
- systemPrompt: effectiveSystemPrompt,
1176
- history: session.getHistory(),
1177
- tools: turnContext.tools,
1178
- signal,
1179
- enablePromptCache: true
1180
- });
1181
- for await (const chunk of stream) {
1182
- if (signal.aborted) {
1183
- throw new TurnAbortedError;
1126
+ analyze() {
1127
+ const readmeInfo = this.extractReadmeMetadata();
1128
+ const projectName = readmeInfo.title || this.detectProjectName();
1129
+ const languages = this.detectLanguages();
1130
+ const packageManager = this.detectPackageManager();
1131
+ const frameworks = [];
1132
+ const infrastructure = [];
1133
+ const commands = {};
1134
+ const architectureNotes = [];
1135
+ const codeConventions = [];
1136
+ let description = readmeInfo.description;
1137
+ const pkgPath = join5(this.cwd, "package.json");
1138
+ if (existsSync6(pkgPath)) {
1139
+ try {
1140
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
1141
+ if (!description && pkg.description)
1142
+ description = pkg.description;
1143
+ const pm = packageManager || "npm";
1144
+ const runPrefix = pm === "bun" || pm === "yarn" || pm === "pnpm" ? `${pm} run` : "npm run";
1145
+ const testPrefix = pm === "bun" ? "bun test" : pm === "pnpm" ? "pnpm test" : pm === "yarn" ? "yarn test" : "npm test";
1146
+ if (pkg.scripts) {
1147
+ if (pkg.scripts.dev)
1148
+ commands.dev = `${runPrefix} dev`;
1149
+ else if (pkg.scripts.start)
1150
+ commands.dev = `${runPrefix} start`;
1151
+ if (pkg.scripts.build)
1152
+ commands.build = `${runPrefix} build`;
1153
+ if (pkg.scripts.test)
1154
+ commands.test = pkg.scripts.test === "bun test" ? "bun test" : testPrefix;
1155
+ if (pkg.scripts.typecheck)
1156
+ commands.typecheck = `${runPrefix} typecheck`;
1157
+ else if (pkg.scripts.check)
1158
+ commands.typecheck = `${runPrefix} check`;
1159
+ if (pkg.scripts.lint)
1160
+ commands.lint = `${runPrefix} lint`;
1161
+ if (pkg.scripts.format)
1162
+ commands.format = `${runPrefix} format`;
1184
1163
  }
1185
- if (chunk.type === "reasoning_delta") {
1186
- session.emitEvent({
1187
- type: "ReasoningDelta",
1188
- turnId,
1189
- delta: chunk.delta
1190
- });
1191
- } else if (chunk.type === "text_delta") {
1192
- currentAgentText += chunk.delta;
1193
- session.emitEvent({
1194
- type: "AgentMessageDelta",
1195
- turnId,
1196
- delta: chunk.delta
1197
- });
1198
- } else if (chunk.type === "tool_call") {
1199
- toolCallRequests.push(chunk);
1200
- } else if (chunk.type === "done") {
1201
- if (chunk.inputTokens !== undefined)
1202
- iterInputTokens = chunk.inputTokens;
1203
- if (chunk.outputTokens !== undefined)
1204
- iterOutputTokens = chunk.outputTokens;
1205
- if (chunk.cachedTokens !== undefined)
1206
- accumulatedCachedTokens += chunk.cachedTokens;
1207
- } else if (chunk.type === "error") {
1208
- throw chunk.error;
1164
+ const allDeps = {
1165
+ ...pkg.dependencies || {},
1166
+ ...pkg.devDependencies || {}
1167
+ };
1168
+ if (allDeps.next)
1169
+ frameworks.push("Next.js");
1170
+ if (allDeps.react)
1171
+ frameworks.push("React");
1172
+ if (allDeps.vue)
1173
+ frameworks.push("Vue.js");
1174
+ if (allDeps.svelte || allDeps["@sveltejs/kit"])
1175
+ frameworks.push("Svelte");
1176
+ if (allDeps.astro)
1177
+ frameworks.push("Astro");
1178
+ if (allDeps.vite)
1179
+ frameworks.push("Vite");
1180
+ if (allDeps.express)
1181
+ frameworks.push("Express");
1182
+ if (allDeps.hono)
1183
+ frameworks.push("Hono");
1184
+ if (allDeps.fastify)
1185
+ frameworks.push("Fastify");
1186
+ if (allDeps["@nestjs/core"])
1187
+ frameworks.push("NestJS");
1188
+ if (allDeps.tailwindcss)
1189
+ frameworks.push("TailwindCSS");
1190
+ if (allDeps["lucide-react"] || allDeps.lucide)
1191
+ frameworks.push("Lucide Icons");
1192
+ if (allDeps.zustand)
1193
+ frameworks.push("Zustand");
1194
+ if (allDeps["@tanstack/react-query"])
1195
+ frameworks.push("TanStack Query");
1196
+ if (allDeps.oxlint)
1197
+ frameworks.push("Oxlint");
1198
+ if (allDeps.eslint)
1199
+ frameworks.push("ESLint");
1200
+ if (allDeps.vitest)
1201
+ frameworks.push("Vitest");
1202
+ if (allDeps.jest)
1203
+ frameworks.push("Jest");
1204
+ if (allDeps.playwright || allDeps["@playwright/test"])
1205
+ frameworks.push("Playwright");
1206
+ if (pkg.type === "module") {
1207
+ codeConventions.push("Use ES modules (`import/export`), not CommonJS (`require`).");
1208
+ }
1209
+ } catch {}
1210
+ }
1211
+ const tsconfigPath = join5(this.cwd, "tsconfig.json");
1212
+ if (existsSync6(tsconfigPath)) {
1213
+ try {
1214
+ const tsconfig = JSON.parse(readFileSync4(tsconfigPath, "utf8"));
1215
+ if (tsconfig.compilerOptions?.strict) {
1216
+ codeConventions.push("TypeScript strict mode enabled.");
1217
+ }
1218
+ if (!commands.typecheck) {
1219
+ commands.typecheck = "tsc --noEmit";
1209
1220
  }
1221
+ } catch {}
1222
+ }
1223
+ const cargoPath = join5(this.cwd, "Cargo.toml");
1224
+ if (existsSync6(cargoPath)) {
1225
+ try {
1226
+ commands.dev = commands.dev || "cargo run";
1227
+ commands.build = commands.build || "cargo build";
1228
+ commands.test = commands.test || "cargo test";
1229
+ commands.lint = commands.lint || "cargo clippy";
1230
+ frameworks.push("Rust Cargo");
1231
+ } catch {}
1232
+ }
1233
+ const goModPath = join5(this.cwd, "go.mod");
1234
+ if (existsSync6(goModPath)) {
1235
+ try {
1236
+ commands.dev = commands.dev || "go run .";
1237
+ commands.build = commands.build || "go build ./...";
1238
+ commands.test = commands.test || "go test ./...";
1239
+ commands.lint = commands.lint || "golangci-lint run";
1240
+ frameworks.push("Go Modules");
1241
+ } catch {}
1242
+ }
1243
+ const pyprojectPath = join5(this.cwd, "pyproject.toml");
1244
+ const requirementsPath = join5(this.cwd, "requirements.txt");
1245
+ if (existsSync6(pyprojectPath) || existsSync6(requirementsPath)) {
1246
+ commands.test = commands.test || "pytest";
1247
+ commands.lint = commands.lint || "ruff check .";
1248
+ if (existsSync6(join5(this.cwd, "uv.lock"))) {
1249
+ frameworks.push("uv");
1250
+ commands.test = "uv run pytest";
1251
+ } else if (existsSync6(join5(this.cwd, "poetry.lock"))) {
1252
+ frameworks.push("Poetry");
1253
+ commands.test = "poetry run pytest";
1210
1254
  }
1211
- if (iterOutputTokens === 0) {
1212
- iterOutputTokens = Math.ceil((currentAgentText.length + JSON.stringify(toolCallRequests).length) / 4);
1255
+ }
1256
+ if (existsSync6(join5(this.cwd, "Dockerfile"))) {
1257
+ infrastructure.push("Docker");
1258
+ const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
1259
+ commands.dockerBuild = `docker build -t ${sanitizedName || "app"} .`;
1260
+ }
1261
+ if (existsSync6(join5(this.cwd, "nginx.conf"))) {
1262
+ infrastructure.push("Nginx");
1263
+ }
1264
+ if (existsSync6(join5(this.cwd, "src/api.ts")) || existsSync6(join5(this.cwd, "src/api"))) {
1265
+ architectureNotes.push("Backend API endpoints and network client logic are centralized in `src/api`.");
1266
+ }
1267
+ if (existsSync6(join5(this.cwd, "src/components"))) {
1268
+ architectureNotes.push("Reusable UI presentation components live in `src/components/`.");
1269
+ }
1270
+ if (existsSync6(join5(this.cwd, "src/types.ts")) || existsSync6(join5(this.cwd, "src/types"))) {
1271
+ architectureNotes.push("Shared TypeScript data models and interfaces are defined in `src/types`.");
1272
+ }
1273
+ if (existsSync6(join5(this.cwd, ".env.example"))) {
1274
+ architectureNotes.push("Environment configuration template is in `.env.example`.");
1275
+ }
1276
+ if (commands.typecheck || commands.lint || commands.test) {
1277
+ const checks = [];
1278
+ if (commands.typecheck)
1279
+ checks.push(`typecheck (\`${commands.typecheck}\`)`);
1280
+ if (commands.lint)
1281
+ checks.push(`lint (\`${commands.lint}\`)`);
1282
+ if (commands.test)
1283
+ checks.push(`tests (\`${commands.test}\`)`);
1284
+ codeConventions.push(`Run ${checks.join(" and ")} before concluding any major code edits.`);
1285
+ }
1286
+ const instructionFiles = ["AGENTS.md", "CLAUDE.md", ".agents.md", "AGENTS.override.md"];
1287
+ let hasExistingInstructions = false;
1288
+ let existingInstructionFile;
1289
+ for (const f of instructionFiles) {
1290
+ if (existsSync6(join5(this.cwd, f))) {
1291
+ hasExistingInstructions = true;
1292
+ existingInstructionFile = f;
1293
+ break;
1213
1294
  }
1214
- accumulatedInputTokens += iterInputTokens;
1215
- accumulatedOutputTokens += iterOutputTokens;
1216
- if (currentAgentText.trim()) {
1217
- const agentItem = {
1218
- id: `msg_agent_${Date.now()}`,
1219
- type: "agent_message",
1220
- content: currentAgentText,
1221
- createdAt: Date.now()
1222
- };
1223
- session.addHistoryItem(agentItem);
1224
- session.emitEvent({
1225
- type: "ItemCompleted",
1226
- turnId,
1227
- item: agentItem
1228
- });
1229
- }
1230
- if (toolCallRequests.length > 0) {
1231
- for (const toolCall of toolCallRequests) {
1232
- if (!toolCall.name || !toolCall.name.trim())
1233
- continue;
1234
- const functionCallItem = {
1235
- id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1236
- type: "function_call",
1237
- callId: toolCall.callId,
1238
- name: toolCall.name,
1239
- arguments: toolCall.arguments,
1240
- createdAt: Date.now()
1241
- };
1242
- session.addHistoryItem(functionCallItem);
1243
- session.emitEvent({
1244
- type: "ItemStarted",
1245
- turnId,
1246
- item: functionCallItem
1247
- });
1248
- session.emitEvent({
1249
- type: "ToolCallStarted",
1250
- turnId,
1251
- toolName: toolCall.name,
1252
- arguments: toolCall.arguments
1253
- });
1254
- const toolResult = await turnContext.tools.execute(toolCall.name, toolCall.arguments, {
1255
- cwd: turnContext.environment.cwd,
1256
- turnId,
1257
- signal,
1258
- execPolicy: session.execPolicy,
1259
- mode: session.collaborationMode,
1260
- permissionMode: session.permissionMode,
1261
- onPlanUpdate: (plan, explanation) => {
1262
- session.emitEvent({
1263
- type: "PlanUpdated",
1264
- turnId,
1265
- explanation,
1266
- plan
1267
- });
1268
- },
1269
- requestApproval: async (description, command, prefixRule) => {
1270
- const approvalId = `appr_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1271
- return session.requestApproval({
1272
- approvalId,
1273
- turnId,
1274
- toolName: toolCall.name,
1275
- description,
1276
- command
1277
- });
1278
- },
1279
- requestInput: async (question, options) => {
1280
- const questionId = `quest_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1281
- return session.requestUserQuestion({
1282
- questionId,
1283
- turnId,
1284
- question,
1285
- options
1286
- });
1287
- }
1288
- });
1289
- const functionOutputItem = {
1290
- id: `out_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1291
- type: "function_call_output",
1292
- callId: toolCall.callId,
1293
- output: toolResult.output,
1294
- isError: toolResult.isError,
1295
- createdAt: Date.now()
1296
- };
1297
- session.addHistoryItem(functionOutputItem);
1298
- session.emitEvent({
1299
- type: "ToolCallFinished",
1300
- turnId,
1301
- toolName: toolCall.name,
1302
- output: toolResult.output,
1303
- isError: toolResult.isError
1304
- });
1305
- session.emitEvent({
1306
- type: "ItemCompleted",
1307
- turnId,
1308
- item: functionOutputItem
1309
- });
1310
- }
1311
- continue;
1312
- }
1313
- if (!currentAgentText.trim() && toolCallRequests.length === 0) {
1314
- if (iteration === 1 && iteration < turnContext.maxIterations) {
1315
- session.addHistoryItem({
1316
- id: `msg_nudge_${Date.now()}`,
1317
- type: "user_message",
1318
- content: `[Systematic ReAct Nudge]: No tool actions or answers were produced in this iteration.
1319
- 1. Review the user's objective and determine the immediate next action.
1320
- 2. If more context is required, invoke an exploration tool ('read_file', 'list_dir', 'grep_search', 'find_files').
1321
- 3. If ready to answer or implement, call the required mutating tool or deliver your full, concrete response now.`,
1322
- createdAt: Date.now()
1323
- });
1324
- continue;
1325
- }
1326
- }
1327
- break;
1328
1295
  }
1329
- const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
1330
- const maxContextTokens = DEFAULT_MAX_CONTEXT_TOKENS;
1331
- session.emitEvent({
1332
- type: "TurnCompleted",
1333
- turnId,
1334
- inputTokens: accumulatedInputTokens,
1335
- outputTokens: accumulatedOutputTokens,
1336
- totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
1337
- cachedTokens: accumulatedCachedTokens > 0 ? accumulatedCachedTokens : undefined,
1338
- contextTokens: totalContextTokens,
1339
- maxContextTokens
1340
- });
1341
- } catch (error) {
1342
- const isAborted = error instanceof TurnAbortedError || signal.aborted;
1343
- const message = isAborted ? "Turn was interrupted" : error instanceof Error ? error.message : String(error);
1344
- session.emitEvent({
1345
- type: "Error",
1346
- turnId,
1347
- message,
1348
- code: isAborted ? "TURN_ABORTED" : "EXECUTION_ERROR"
1349
- });
1350
- } finally {
1351
- session.clearActiveTurn(turnId);
1352
- globalEphemeralWorkspace.cleanupTurn(turnId);
1353
- globalEphemeralWorkspace.cleanRootResidue(turnContext.environment.cwd);
1354
- }
1355
- }
1356
-
1357
- // src/session/turn-input.ts
1358
- async function handleTurnInput(session, request) {
1359
- const activeTurn = session.getActiveTurn();
1360
- if (!activeTurn) {
1361
- const turnId = `turn_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
1362
- const turnContext = new TurnContext(turnId, session.model, session.tools, { cwd: session.cwd });
1363
- session.setActiveTurn(turnContext);
1364
- runTurn(session, turnContext, request).catch((err) => {
1365
- session.emitEvent({
1366
- type: "Error",
1367
- turnId,
1368
- message: `Unhandled turn error: ${err instanceof Error ? err.message : String(err)}`
1369
- });
1370
- });
1371
1296
  return {
1372
- kind: "started",
1373
- turnId
1297
+ projectName,
1298
+ description,
1299
+ languages,
1300
+ packageManager,
1301
+ frameworks,
1302
+ infrastructure,
1303
+ commands,
1304
+ architectureNotes,
1305
+ codeConventions,
1306
+ hasExistingInstructions,
1307
+ existingInstructionFile
1374
1308
  };
1375
1309
  }
1376
- session.addHistoryItem({
1377
- id: `steer_${Date.now()}`,
1378
- type: "user_message",
1379
- content: `[Steering Guidance]: ${request.text}`,
1380
- images: request.images,
1381
- createdAt: Date.now()
1382
- });
1383
- return {
1384
- kind: "steered",
1385
- turnId: activeTurn.turnId
1386
- };
1387
- }
1388
-
1389
- // src/session/submission-loop.ts
1390
- async function submissionLoop(session, queue) {
1391
- for await (const submission of queue) {
1392
- const { op } = submission;
1393
- switch (op.type) {
1394
- case "TurnInput": {
1395
- const text = op.request?.text ?? op.prompt ?? (typeof op.request === "string" ? op.request : "");
1396
- const images = op.request?.images ?? op.images;
1397
- await handleTurnInput(session, {
1398
- text,
1399
- images,
1400
- clientId: op.request?.clientId,
1401
- additionalContext: op.request?.additionalContext
1402
- });
1403
- break;
1404
- }
1405
- case "Interrupt": {
1406
- session.interrupt();
1407
- break;
1408
- }
1409
- case "ExecApproval": {
1410
- session.resolveApproval(op.approvalId, op.approved);
1411
- break;
1412
- }
1413
- case "Shutdown": {
1414
- session.interrupt();
1415
- session.emitEvent({
1416
- type: "StatusChanged",
1417
- status: "terminated"
1418
- });
1419
- return;
1310
+ generateAgentsMarkdown(analysis) {
1311
+ const lines = [];
1312
+ lines.push(`# ${analysis.projectName}`);
1313
+ lines.push("");
1314
+ if (analysis.description) {
1315
+ lines.push(`> ${analysis.description}`);
1316
+ lines.push("");
1317
+ }
1318
+ lines.push("## Commands");
1319
+ lines.push("");
1320
+ if (Object.keys(analysis.commands).length > 0) {
1321
+ if (analysis.commands.dev)
1322
+ lines.push(`- **Dev Server**: \`${analysis.commands.dev}\``);
1323
+ if (analysis.commands.build)
1324
+ lines.push(`- **Build**: \`${analysis.commands.build}\``);
1325
+ if (analysis.commands.test)
1326
+ lines.push(`- **Test**: \`${analysis.commands.test}\``);
1327
+ if (analysis.commands.typecheck)
1328
+ lines.push(`- **Typecheck**: \`${analysis.commands.typecheck}\``);
1329
+ if (analysis.commands.lint)
1330
+ lines.push(`- **Lint**: \`${analysis.commands.lint}\``);
1331
+ if (analysis.commands.format)
1332
+ lines.push(`- **Format**: \`${analysis.commands.format}\``);
1333
+ if (analysis.commands.dockerBuild)
1334
+ lines.push(`- **Docker Build**: \`${analysis.commands.dockerBuild}\``);
1335
+ } else {
1336
+ lines.push("- *No standard build/test commands detected.*");
1337
+ }
1338
+ lines.push("");
1339
+ lines.push("## Architecture & Stack");
1340
+ lines.push("");
1341
+ const stackItems = [];
1342
+ if (analysis.languages.length > 0)
1343
+ stackItems.push(analysis.languages.join(", "));
1344
+ if (analysis.frameworks.length > 0)
1345
+ stackItems.push(analysis.frameworks.join(", "));
1346
+ if (analysis.infrastructure.length > 0)
1347
+ stackItems.push(analysis.infrastructure.join(", "));
1348
+ if (stackItems.length > 0) {
1349
+ lines.push(`- **Core Stack**: ${stackItems.join(" \u2022 ")}`);
1350
+ }
1351
+ for (const note of analysis.architectureNotes) {
1352
+ lines.push(`- ${note}`);
1353
+ }
1354
+ lines.push("");
1355
+ lines.push("## Workflow & Code Guidelines");
1356
+ lines.push("");
1357
+ if (analysis.codeConventions.length > 0) {
1358
+ for (const conv of analysis.codeConventions) {
1359
+ lines.push(`- ${conv}`);
1420
1360
  }
1421
1361
  }
1362
+ lines.push("- Prefer targeted edits over whole-file rewrites.");
1363
+ lines.push("- When fixing errors, address the root cause rather than suppressing compiler warnings.");
1364
+ lines.push("");
1365
+ return lines.join(`
1366
+ `);
1422
1367
  }
1423
- }
1424
-
1425
- // src/security/exec-policy.ts
1426
- class ExecPolicy {
1427
- rules = [];
1428
- mode = "auto";
1429
- constructor(initialMode = "auto") {
1430
- this.mode = initialMode;
1431
- this.initDefaultRules();
1432
- }
1433
- getMode() {
1434
- return this.mode;
1435
- }
1436
- setMode(mode) {
1437
- this.mode = mode;
1438
- }
1439
- initDefaultRules() {
1440
- this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
1441
- this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
1442
- this.addRule(/^(bun\s+(test|--version|-v)|npm\s+(test|--version|-v)|node\s+-v)\b/i, "allow", "Testing & runtime check");
1443
- this.addRule(/^(rm|del|rmdir|format|mkfs)\b/i, "prompt", "Destructive file removal");
1444
- this.addRule(/^(git\s+(push|reset\s+--hard|clean\s+-fd|rebase))\b/i, "prompt", "Destructive git operation");
1445
- this.addRule(/^(curl|wget|fetch|ssh|scp|ftp)\b/i, "prompt", "Network / remote transfer");
1446
- }
1447
- addRule(pattern, decision, description) {
1448
- this.rules.unshift({ pattern, decision, description });
1368
+ extractReadmeMetadata() {
1369
+ const readmeFiles = ["README.md", "readme.md", "README.MD"];
1370
+ for (const file of readmeFiles) {
1371
+ const fullPath = join5(this.cwd, file);
1372
+ if (existsSync6(fullPath)) {
1373
+ try {
1374
+ const content = readFileSync4(fullPath, "utf8");
1375
+ const lines = content.split(`
1376
+ `);
1377
+ let title;
1378
+ let description;
1379
+ for (const line of lines) {
1380
+ const trimmed = line.trim();
1381
+ if (!title && trimmed.startsWith("# ")) {
1382
+ title = trimmed.replace(/^#\s+/, "").trim();
1383
+ continue;
1384
+ }
1385
+ if (title && !description && trimmed.length > 0 && !trimmed.startsWith("#") && !trimmed.startsWith("```") && !trimmed.startsWith("[")) {
1386
+ description = trimmed;
1387
+ break;
1388
+ }
1389
+ }
1390
+ return { title, description };
1391
+ } catch {}
1392
+ }
1393
+ }
1394
+ return {};
1449
1395
  }
1450
- shouldPromptFileEdit(filePath) {
1451
- if (this.mode === "plan") {
1452
- return {
1453
- prompt: true,
1454
- isPlanBlocked: true,
1455
- reason: `[Plan Mode Gate] Approval required to mutate '${filePath || "file"}' and proceed with implementation.`
1456
- };
1396
+ detectProjectName() {
1397
+ const pkgPath = join5(this.cwd, "package.json");
1398
+ if (existsSync6(pkgPath)) {
1399
+ try {
1400
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
1401
+ if (pkg.name && pkg.name !== "frontend" && pkg.name !== "backend" && pkg.name !== "app") {
1402
+ return pkg.name.startsWith("@") ? pkg.name.split("/")[1] || pkg.name : pkg.name;
1403
+ }
1404
+ } catch {}
1457
1405
  }
1458
- if (this.mode === "manual") {
1459
- return {
1460
- prompt: true,
1461
- reason: `Manual mode requires approval to modify '${filePath || "file"}'`
1462
- };
1406
+ const cargoPath = join5(this.cwd, "Cargo.toml");
1407
+ if (existsSync6(cargoPath)) {
1408
+ try {
1409
+ const match = readFileSync4(cargoPath, "utf8").match(/name\s*=\s*"([^"]+)"/);
1410
+ if (match?.[1])
1411
+ return match[1];
1412
+ } catch {}
1463
1413
  }
1464
- return { prompt: false };
1414
+ const goModPath = join5(this.cwd, "go.mod");
1415
+ if (existsSync6(goModPath)) {
1416
+ try {
1417
+ const match = readFileSync4(goModPath, "utf8").match(/module\s+([^\s]+)/);
1418
+ if (match?.[1])
1419
+ return basename(match[1]);
1420
+ } catch {}
1421
+ }
1422
+ return basename(this.cwd);
1465
1423
  }
1466
- evaluate(command) {
1467
- const trimmed = command.trim();
1468
- if (this.mode === "plan") {
1469
- 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);
1470
- if (isReadOnly) {
1471
- return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
1472
- }
1473
- return {
1474
- decision: "prompt",
1475
- reason: `[Plan Mode Gate] Approval required to execute shell command '${trimmed}' in Plan Mode`
1476
- };
1424
+ detectLanguages() {
1425
+ const langs = new Set;
1426
+ if (existsSync6(join5(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
1427
+ langs.add("TypeScript");
1477
1428
  }
1478
- if (this.mode === "manual") {
1479
- return {
1480
- decision: "prompt",
1481
- reason: "Manual mode requires confirmation for all shell commands"
1482
- };
1429
+ if (existsSync6(join5(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
1430
+ langs.add("JavaScript");
1483
1431
  }
1484
- if (this.mode === "accept-edits") {
1485
- const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|bun\s+test|npm\s+test)\b/i.test(trimmed);
1486
- if (isReadOnly) {
1487
- return { decision: "allow", reason: "Safe read-only command in accept-edits mode" };
1488
- }
1489
- return {
1490
- decision: "prompt",
1491
- reason: "Accept-edits mode requires approval for active shell commands"
1492
- };
1432
+ if (existsSync6(join5(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
1433
+ langs.add("Rust");
1493
1434
  }
1494
- for (const rule of this.rules) {
1495
- if (rule.pattern.test(trimmed)) {
1496
- return {
1497
- decision: rule.decision,
1498
- reason: rule.description
1499
- };
1500
- }
1435
+ if (existsSync6(join5(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
1436
+ langs.add("Go");
1437
+ }
1438
+ if (existsSync6(join5(this.cwd, "pyproject.toml")) || existsSync6(join5(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
1439
+ langs.add("Python");
1440
+ }
1441
+ if (existsSync6(join5(this.cwd, "pom.xml")) || existsSync6(join5(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
1442
+ langs.add("Java");
1443
+ }
1444
+ if (existsSync6(join5(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
1445
+ langs.add("C/C++");
1446
+ }
1447
+ return Array.from(langs);
1448
+ }
1449
+ detectPackageManager() {
1450
+ if (existsSync6(join5(this.cwd, "bun.lockb")) || existsSync6(join5(this.cwd, "bun.lock")))
1451
+ return "bun";
1452
+ if (existsSync6(join5(this.cwd, "pnpm-lock.yaml")))
1453
+ return "pnpm";
1454
+ if (existsSync6(join5(this.cwd, "yarn.lock")))
1455
+ return "yarn";
1456
+ if (existsSync6(join5(this.cwd, "package-lock.json")))
1457
+ return "npm";
1458
+ if (existsSync6(join5(this.cwd, "Cargo.lock")) || existsSync6(join5(this.cwd, "Cargo.toml")))
1459
+ return "cargo";
1460
+ if (existsSync6(join5(this.cwd, "uv.lock")))
1461
+ return "uv";
1462
+ if (existsSync6(join5(this.cwd, "poetry.lock")))
1463
+ return "poetry";
1464
+ if (existsSync6(join5(this.cwd, "go.sum")) || existsSync6(join5(this.cwd, "go.mod")))
1465
+ return "go";
1466
+ if (existsSync6(join5(this.cwd, "package.json")))
1467
+ return "npm";
1468
+ return;
1469
+ }
1470
+ hasFileWithExtension(...exts) {
1471
+ try {
1472
+ const entries = readdirSync3(this.cwd);
1473
+ return entries.some((e) => exts.some((ext) => e.endsWith(ext)));
1474
+ } catch {
1475
+ return false;
1501
1476
  }
1502
- return {
1503
- decision: "allow",
1504
- reason: "Auto mode allows execution"
1505
- };
1506
1477
  }
1507
1478
  }
1508
1479
 
1509
- // src/session/session.ts
1510
- class Session {
1511
- threadId;
1512
- model;
1480
+ // src/verification/verifier.ts
1481
+ class AutoVerifier {
1513
1482
  cwd;
1514
- systemPrompt;
1515
- modelClient;
1516
- tools;
1517
- skillsLoader;
1518
- memoryStore;
1519
- mcpManager;
1520
- execPolicy;
1521
- collaborationMode = "default";
1522
- get permissionMode() {
1523
- return this.execPolicy.getMode();
1483
+ customCommand;
1484
+ timeoutMs;
1485
+ constructor(options) {
1486
+ this.cwd = options.cwd;
1487
+ this.customCommand = options.customCommand;
1488
+ this.timeoutMs = options.timeoutMs ?? 30000;
1524
1489
  }
1525
- setPermissionMode(mode) {
1526
- this.execPolicy.setMode(mode);
1527
- if (mode === "plan") {
1528
- this.collaborationMode = "plan";
1529
- } else if (this.collaborationMode === "plan") {
1530
- this.collaborationMode = "default";
1490
+ resolveVerificationCommand() {
1491
+ if (this.customCommand && this.customCommand.trim()) {
1492
+ return this.customCommand.trim();
1531
1493
  }
1532
- }
1533
- history = [];
1534
- activeTurn = null;
1535
- status = "idle";
1536
- eventListeners = [];
1537
- pendingApprovals = new Map;
1538
- pendingUserQuestions = new Map;
1539
- submissionResolvers = [];
1540
- submissionQueue = [];
1541
- isTerminated = false;
1542
- constructor(options = {}) {
1543
- this.threadId = options.threadId || `thread_${Date.now()}`;
1544
- this.model = options.model || "gpt-4o";
1545
- this.cwd = options.cwd || process.cwd();
1546
- this.systemPrompt = options.systemPrompt || "";
1547
- this.modelClient = options.modelClient || new ModelClient;
1548
- this.tools = options.tools || new ToolRouter;
1549
- this.skillsLoader = options.skillsLoader;
1550
- this.memoryStore = options.memoryStore;
1551
- this.mcpManager = options.mcpManager;
1552
- this.execPolicy = options.execPolicy || new ExecPolicy;
1553
- this.collaborationMode = options.collaborationMode || "default";
1554
- this.history = options.initialHistory ? [...options.initialHistory] : [];
1555
- if (options.onEvent) {
1556
- this.eventListeners.push(options.onEvent);
1494
+ try {
1495
+ const analyzer = new ProjectAnalyzer(this.cwd);
1496
+ const analysis = analyzer.analyze();
1497
+ if (analysis.commands.typecheck) {
1498
+ return analysis.commands.typecheck;
1499
+ }
1500
+ if (analysis.commands.lint) {
1501
+ return analysis.commands.lint;
1502
+ }
1503
+ if (analysis.commands.test) {
1504
+ return analysis.commands.test;
1505
+ }
1506
+ } catch {}
1507
+ const pkgPath = join6(this.cwd, "package.json");
1508
+ if (existsSync7(pkgPath)) {
1509
+ try {
1510
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1511
+ if (pkg.scripts) {
1512
+ if (pkg.scripts.typecheck)
1513
+ return "npm run typecheck";
1514
+ if (pkg.scripts.check)
1515
+ return "npm run check";
1516
+ if (pkg.scripts.test)
1517
+ return "npm test";
1518
+ }
1519
+ } catch {}
1557
1520
  }
1558
- this.startSubmissionLoop();
1559
- this.emitEvent({
1560
- type: "SessionConfigured",
1561
- threadId: this.threadId,
1562
- model: this.model
1563
- });
1564
- }
1565
- onEvent(listener) {
1566
- this.eventListeners.push(listener);
1567
- return () => {
1568
- this.eventListeners = this.eventListeners.filter((l) => l !== listener);
1569
- };
1570
- }
1571
- emitEvent(msg) {
1572
- if (msg.type === "StatusChanged") {
1573
- this.status = msg.status;
1521
+ if (existsSync7(join6(this.cwd, "tsconfig.json"))) {
1522
+ return "npx tsc --noEmit";
1574
1523
  }
1575
- const event = {
1576
- id: `evt_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1577
- timestamp: Date.now(),
1578
- msg
1579
- };
1580
- for (const listener of this.eventListeners) {
1581
- try {
1582
- listener(event);
1583
- } catch (err) {
1584
- console.error("Error in event listener:", err);
1524
+ if (existsSync7(join6(this.cwd, "Cargo.toml"))) {
1525
+ return "cargo check";
1526
+ }
1527
+ if (existsSync7(join6(this.cwd, "go.mod"))) {
1528
+ return "go vet ./...";
1529
+ }
1530
+ if (existsSync7(join6(this.cwd, "pyproject.toml")) || existsSync7(join6(this.cwd, "setup.py"))) {
1531
+ if (existsSync7(join6(this.cwd, "mypy.ini")) || existsSync7(join6(this.cwd, ".mypy.ini"))) {
1532
+ return "mypy .";
1585
1533
  }
1586
1534
  }
1535
+ return null;
1587
1536
  }
1588
- getHistory() {
1589
- return [...this.history];
1590
- }
1591
- setHistory(items) {
1592
- this.history = [...items];
1593
- }
1594
- addHistoryItem(item) {
1595
- this.history.push(item);
1596
- }
1597
- getActiveTurn() {
1598
- return this.activeTurn;
1599
- }
1600
- setActiveTurn(turn) {
1601
- this.activeTurn = turn;
1602
- this.emitEvent({
1603
- type: "StatusChanged",
1604
- status: "running"
1605
- });
1606
- }
1607
- clearActiveTurn(turnId) {
1608
- if (!turnId || this.activeTurn?.turnId === turnId) {
1609
- this.activeTurn = null;
1610
- this.emitEvent({
1611
- type: "StatusChanged",
1612
- status: "idle"
1613
- });
1614
- }
1615
- }
1616
- interrupt() {
1617
- if (this.activeTurn) {
1618
- this.activeTurn.abort("Interrupted by user");
1619
- this.clearActiveTurn();
1620
- this.emitEvent({
1621
- type: "StatusChanged",
1622
- status: "interrupted"
1623
- });
1624
- }
1625
- }
1626
- requestApproval(params) {
1627
- this.emitEvent({
1628
- type: "ApprovalRequired",
1629
- approvalId: params.approvalId,
1630
- turnId: params.turnId,
1631
- toolName: params.toolName,
1632
- description: params.description,
1633
- command: params.command
1634
- });
1635
- this.emitEvent({
1636
- type: "StatusChanged",
1637
- status: "waiting_approval"
1638
- });
1639
- return new Promise((resolve) => {
1640
- this.pendingApprovals.set(params.approvalId, (approved) => {
1641
- this.emitEvent({
1642
- type: "StatusChanged",
1643
- status: "running"
1644
- });
1645
- resolve(approved);
1646
- });
1647
- });
1648
- }
1649
- resolveApproval(approvalId, approved) {
1650
- const resolver = this.pendingApprovals.get(approvalId);
1651
- if (resolver) {
1652
- this.pendingApprovals.delete(approvalId);
1653
- resolver(approved);
1654
- }
1655
- }
1656
- requestUserQuestion(params) {
1657
- this.emitEvent({
1658
- type: "UserQuestionRequired",
1659
- questionId: params.questionId,
1660
- turnId: params.turnId,
1661
- question: params.question,
1662
- options: params.options
1663
- });
1664
- this.emitEvent({
1665
- type: "StatusChanged",
1666
- status: "waiting_user_input"
1667
- });
1668
- return new Promise((resolve) => {
1669
- this.pendingUserQuestions.set(params.questionId, (answer) => {
1670
- this.emitEvent({
1671
- type: "StatusChanged",
1672
- status: "running"
1673
- });
1674
- resolve(answer);
1675
- });
1676
- });
1677
- }
1678
- resolveUserQuestion(questionId, answer) {
1679
- const resolver = this.pendingUserQuestions.get(questionId);
1680
- if (resolver) {
1681
- this.pendingUserQuestions.delete(questionId);
1682
- resolver(answer);
1683
- }
1684
- }
1685
- async submit(op) {
1686
- const subId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1687
- const submission = {
1688
- id: subId,
1689
- op,
1690
- createdAt: Date.now()
1691
- };
1692
- const resolver = this.submissionResolvers.shift();
1693
- if (resolver) {
1694
- resolver(submission);
1695
- } else {
1696
- this.submissionQueue.push(submission);
1537
+ verify(modifiedFiles = []) {
1538
+ const command = this.resolveVerificationCommand();
1539
+ const startTime = performance.now();
1540
+ if (!command) {
1541
+ return {
1542
+ command: "none",
1543
+ success: true,
1544
+ exitCode: 0,
1545
+ output: "No automated verification command configured or detected for this workspace.",
1546
+ durationMs: 0,
1547
+ reason: "NO_VERIFIER_DETECTED"
1548
+ };
1697
1549
  }
1698
- return subId;
1699
- }
1700
- async prompt(text, images) {
1701
- return handleTurnInput(this, { text, images });
1702
- }
1703
- async promptAndWait(text, images, timeoutMs = 30000) {
1704
- return new Promise((resolve, reject) => {
1705
- const timer = setTimeout(() => {
1706
- unsub();
1707
- reject(new Error(`Turn timed out after ${timeoutMs}ms`));
1708
- }, timeoutMs);
1709
- const unsub = this.onEvent((event) => {
1710
- if (event.msg.type === "TurnCompleted") {
1711
- clearTimeout(timer);
1712
- unsub();
1713
- resolve();
1714
- } else if (event.msg.type === "Error") {
1715
- clearTimeout(timer);
1716
- unsub();
1717
- reject(new Error(event.msg.message));
1550
+ try {
1551
+ const isWindows = process.platform === "win32";
1552
+ const proc = spawnSync(command, {
1553
+ cwd: this.cwd,
1554
+ shell: true,
1555
+ encoding: "utf8",
1556
+ timeout: this.timeoutMs,
1557
+ maxBuffer: 10 * 1024 * 1024,
1558
+ env: {
1559
+ ...process.env,
1560
+ CI: "true",
1561
+ FORCE_COLOR: "0"
1718
1562
  }
1719
1563
  });
1720
- this.prompt(text, images).catch((err) => {
1721
- clearTimeout(timer);
1722
- unsub();
1723
- reject(err);
1724
- });
1725
- });
1726
- }
1727
- async* createSubmissionIterator() {
1728
- while (!this.isTerminated) {
1729
- if (this.submissionQueue.length > 0) {
1730
- yield this.submissionQueue.shift();
1731
- } else {
1732
- const nextSub = await new Promise((resolve) => {
1733
- this.submissionResolvers.push(resolve);
1734
- });
1735
- yield nextSub;
1736
- }
1737
- }
1738
- }
1739
- startSubmissionLoop() {
1740
- const iterator = this.createSubmissionIterator();
1741
- submissionLoop(this, iterator).catch((err) => {
1742
- console.error("Submission loop terminated with error:", err);
1743
- });
1744
- }
1745
- }
1746
- // src/tools/handlers/apply-patch.ts
1747
- import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1748
- import { resolve as resolve5, dirname as dirname3 } from "path";
1749
- import { mkdirSync as mkdirSync4 } from "fs";
1750
- var applyPatchTool = {
1751
- name: "apply_patch",
1752
- description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
1753
- parameters: {
1754
- type: "object",
1755
- properties: {
1756
- path: {
1757
- type: "string",
1758
- description: "Relative or absolute path to the target file"
1759
- },
1760
- targetContent: {
1761
- type: "string",
1762
- description: "The exact block of code in the file to be replaced. For creating a new file, leave this empty."
1763
- },
1764
- replacementContent: {
1765
- type: "string",
1766
- description: "The new code content to replace the targetContent with."
1767
- }
1768
- },
1769
- required: ["path", "replacementContent"]
1770
- },
1771
- async execute(args, ctx) {
1772
- const rawPath = String(args.path || "");
1773
- if (!rawPath) {
1774
- return { output: "Error: 'path' parameter is required", isError: true };
1775
- }
1776
- const filePath = resolve5(ctx.cwd, rawPath);
1777
- const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
1778
- const replacementContent = String(args.replacementContent ?? "");
1779
- if (ctx.execPolicy) {
1780
- const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
1781
- if (evalResult.prompt && ctx.requestApproval) {
1782
- const approval = await ctx.requestApproval(evalResult.reason || `Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
1783
- const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
1784
- if (!allowed) {
1785
- return {
1786
- output: `[Plan Mode Gate]: File modification declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
1787
- isError: true
1788
- };
1564
+ const durationMs = Math.round(performance.now() - startTime);
1565
+ const stdout = proc.stdout ? String(proc.stdout) : "";
1566
+ const stderr = proc.stderr ? String(proc.stderr) : "";
1567
+ let combined = (stdout + `
1568
+ ` + stderr).trim();
1569
+ if (combined.length > 3000) {
1570
+ const lines = combined.split(`
1571
+ `);
1572
+ if (lines.length > 60) {
1573
+ const head = lines.slice(0, 30).join(`
1574
+ `);
1575
+ const tail = lines.slice(-25).join(`
1576
+ `);
1577
+ combined = `${head}
1578
+
1579
+ ... [${lines.length - 55} lines truncated for context efficiency] ...
1580
+
1581
+ ${tail}`;
1789
1582
  }
1790
- } else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
1791
- return {
1792
- output: `[Plan Mode Gate]: Cannot mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
1793
- isError: true
1794
- };
1795
- }
1796
- }
1797
- if (!existsSync6(filePath)) {
1798
- if (targetContent) {
1799
- return {
1800
- output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.
1801
- [Systematic Error Recovery Checklist]:
1802
- 1. Root Cause: Trying to patch a non-existent file with targetContent.
1803
- 2. Fix: For creating new files, leave 'targetContent' empty and provide full contents in 'replacementContent'.
1804
- 3. Alternatively, check if the file path '${rawPath}' was mistyped.`,
1805
- isError: true
1806
- };
1807
- }
1808
- try {
1809
- mkdirSync4(dirname3(filePath), { recursive: true });
1810
- writeFileSync2(filePath, replacementContent, "utf8");
1811
- return { output: `Successfully created new file '${rawPath}'` };
1812
- } catch (err) {
1813
- return {
1814
- output: `Failed to create file '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
1815
- isError: true
1816
- };
1817
- }
1818
- }
1819
- try {
1820
- const originalFileContent = readFileSync4(filePath, "utf8");
1821
- if (!targetContent) {
1822
- return {
1823
- output: `Error: File '${rawPath}' already exists, but targetContent was empty.
1824
- [Systematic Error Recovery Checklist]:
1825
- 1. Root Cause: An existing file requires targetContent to specify which lines to replace.
1826
- 2. Fix: Call 'read_file' on '${rawPath}', extract the exact target lines, and provide them in 'targetContent'.
1827
- 3. To overwrite the whole file, use the 'write_file' tool instead.`,
1828
- isError: true
1829
- };
1830
- }
1831
- const firstIndex = originalFileContent.indexOf(targetContent);
1832
- if (firstIndex === -1) {
1833
- return {
1834
- output: `Error: targetContent was not found in '${rawPath}'.
1835
- [Systematic Error Recovery Checklist]:
1836
- 1. Root Cause: The snippet in targetContent does not match the actual file content (differences in whitespace, indentation, line endings, or prior edits).
1837
- 2. Action: Call 'read_file' on '${rawPath}' to inspect current exact lines and indentation.
1838
- 3. Fix: Provide the exact matching lines (including leading spaces) or wider context, then retry 'apply_patch'.`,
1839
- isError: true
1840
- };
1841
- }
1842
- const secondIndex = originalFileContent.indexOf(targetContent, firstIndex + 1);
1843
- if (secondIndex !== -1) {
1844
- return {
1845
- output: `Error: targetContent matched multiple locations in '${rawPath}'.
1846
- [Systematic Error Recovery Checklist]:
1847
- 1. Root Cause: targetContent is ambiguous and occurs multiple times in the file.
1848
- 2. Action: Include 2-3 additional surrounding lines (before or after the target block) to make the target snippet uniquely identifiable.
1849
- 3. Fix: Re-run 'apply_patch' with the extended unique block.`,
1850
- isError: true
1851
- };
1852
1583
  }
1853
- const newFileContent = originalFileContent.slice(0, firstIndex) + replacementContent + originalFileContent.slice(firstIndex + targetContent.length);
1854
- writeFileSync2(filePath, newFileContent, "utf8");
1584
+ const exitCode = proc.status ?? (proc.error ? 1 : 0);
1585
+ const success = exitCode === 0;
1855
1586
  return {
1856
- output: `Successfully applied patch to '${rawPath}'`
1587
+ command,
1588
+ success,
1589
+ exitCode,
1590
+ output: combined || (success ? "Verification succeeded cleanly." : "Command failed with empty output."),
1591
+ durationMs
1857
1592
  };
1858
1593
  } catch (err) {
1594
+ const durationMs = Math.round(performance.now() - startTime);
1859
1595
  return {
1860
- output: `Failed to apply patch to '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
1861
- isError: true
1596
+ command,
1597
+ success: false,
1598
+ exitCode: 1,
1599
+ output: `Verification execution error: ${err.message || String(err)}`,
1600
+ durationMs
1862
1601
  };
1863
1602
  }
1864
1603
  }
1865
- };
1866
- // src/security/kernel/windows.ts
1604
+ }
1605
+ // src/session/turn.ts
1606
+ async function runTurn(session, turnContext, input) {
1607
+ const { turnId, signal } = turnContext;
1608
+ session.emitEvent({
1609
+ type: "TurnStarted",
1610
+ turnId
1611
+ });
1612
+ const currentHistory = session.getHistory();
1613
+ const estimatedTokens = estimateTotalTokens(currentHistory);
1614
+ const maxTokenLimit = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS;
1615
+ if (estimatedTokens > maxTokenLimit) {
1616
+ const compacted = compactHistory(currentHistory);
1617
+ session.setHistory(compacted);
1618
+ session.emitEvent({
1619
+ type: "Warning",
1620
+ message: `Auto-compacted conversation history (${estimatedTokens} estimated tokens exceeded limit).`
1621
+ });
1622
+ }
1623
+ const userItem = {
1624
+ id: `msg_user_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1625
+ type: "user_message",
1626
+ content: typeof input === "string" ? input : input?.text ?? input?.prompt ?? "",
1627
+ images: typeof input === "object" ? input?.images : undefined,
1628
+ createdAt: Date.now()
1629
+ };
1630
+ session.addHistoryItem(userItem);
1631
+ session.emitEvent({
1632
+ type: "ItemCompleted",
1633
+ turnId,
1634
+ item: userItem
1635
+ });
1636
+ const cwd = turnContext.environment.cwd;
1637
+ const worldState = await captureWorldState(cwd);
1638
+ const memoriesPrompt = session.memoryStore?.formatMemoriesPrompt(cwd);
1639
+ const skillsPrompt = session.skillsLoader?.formatSkillsPrompt(cwd);
1640
+ const mcpPrompt = session.mcpManager?.formatMcpPrompt();
1641
+ const effectiveSystemPrompt = buildSystemPrompt({
1642
+ basePrompt: session.systemPrompt || undefined,
1643
+ worldStatePrompt: formatWorldStatePrompt(worldState),
1644
+ memoriesPrompt,
1645
+ skillsPrompt,
1646
+ mcpPrompt,
1647
+ isOrchestrator: session.tools.has("spawn_agent"),
1648
+ cwd
1649
+ });
1650
+ let iteration = 0;
1651
+ let accumulatedInputTokens = 0;
1652
+ let accumulatedOutputTokens = 0;
1653
+ let accumulatedCachedTokens = 0;
1654
+ const clientSession = session.modelClient.newSession();
1655
+ const modifiedFiles = new Set;
1656
+ let selfHealingAttempts = 0;
1657
+ let hasRunVerification = false;
1658
+ try {
1659
+ while (iteration < turnContext.maxIterations) {
1660
+ if (signal.aborted) {
1661
+ throw new TurnAbortedError;
1662
+ }
1663
+ iteration++;
1664
+ let currentAgentText = "";
1665
+ const toolCallRequests = [];
1666
+ let iterInputTokens = Math.ceil((effectiveSystemPrompt.length + JSON.stringify(session.getHistory()).length) / 4);
1667
+ let iterOutputTokens = 0;
1668
+ const stream = clientSession.stream({
1669
+ model: turnContext.model,
1670
+ systemPrompt: effectiveSystemPrompt,
1671
+ history: session.getHistory(),
1672
+ tools: turnContext.tools,
1673
+ signal,
1674
+ enablePromptCache: true
1675
+ });
1676
+ for await (const chunk of stream) {
1677
+ if (signal.aborted) {
1678
+ throw new TurnAbortedError;
1679
+ }
1680
+ if (chunk.type === "reasoning_delta") {
1681
+ session.emitEvent({
1682
+ type: "ReasoningDelta",
1683
+ turnId,
1684
+ delta: chunk.delta
1685
+ });
1686
+ } else if (chunk.type === "text_delta") {
1687
+ currentAgentText += chunk.delta;
1688
+ session.emitEvent({
1689
+ type: "AgentMessageDelta",
1690
+ turnId,
1691
+ delta: chunk.delta
1692
+ });
1693
+ } else if (chunk.type === "tool_call") {
1694
+ toolCallRequests.push(chunk);
1695
+ } else if (chunk.type === "done") {
1696
+ if (chunk.inputTokens !== undefined)
1697
+ iterInputTokens = chunk.inputTokens;
1698
+ if (chunk.outputTokens !== undefined)
1699
+ iterOutputTokens = chunk.outputTokens;
1700
+ if (chunk.cachedTokens !== undefined)
1701
+ accumulatedCachedTokens += chunk.cachedTokens;
1702
+ } else if (chunk.type === "error") {
1703
+ throw chunk.error;
1704
+ }
1705
+ }
1706
+ if (iterOutputTokens === 0) {
1707
+ iterOutputTokens = Math.ceil((currentAgentText.length + JSON.stringify(toolCallRequests).length) / 4);
1708
+ }
1709
+ accumulatedInputTokens += iterInputTokens;
1710
+ accumulatedOutputTokens += iterOutputTokens;
1711
+ if (currentAgentText.trim()) {
1712
+ const agentItem = {
1713
+ id: `msg_agent_${Date.now()}`,
1714
+ type: "agent_message",
1715
+ content: currentAgentText,
1716
+ createdAt: Date.now()
1717
+ };
1718
+ session.addHistoryItem(agentItem);
1719
+ session.emitEvent({
1720
+ type: "ItemCompleted",
1721
+ turnId,
1722
+ item: agentItem
1723
+ });
1724
+ }
1725
+ if (toolCallRequests.length > 0) {
1726
+ for (const toolCall of toolCallRequests) {
1727
+ if (!toolCall.name || !toolCall.name.trim())
1728
+ continue;
1729
+ const functionCallItem = {
1730
+ id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1731
+ type: "function_call",
1732
+ callId: toolCall.callId,
1733
+ name: toolCall.name,
1734
+ arguments: toolCall.arguments,
1735
+ createdAt: Date.now()
1736
+ };
1737
+ session.addHistoryItem(functionCallItem);
1738
+ session.emitEvent({
1739
+ type: "ItemStarted",
1740
+ turnId,
1741
+ item: functionCallItem
1742
+ });
1743
+ session.emitEvent({
1744
+ type: "ToolCallStarted",
1745
+ turnId,
1746
+ toolName: toolCall.name,
1747
+ arguments: toolCall.arguments
1748
+ });
1749
+ const toolResult = await turnContext.tools.execute(toolCall.name, toolCall.arguments, {
1750
+ cwd: turnContext.environment.cwd,
1751
+ turnId,
1752
+ signal,
1753
+ execPolicy: session.execPolicy,
1754
+ mode: session.collaborationMode,
1755
+ permissionMode: session.permissionMode,
1756
+ onPlanUpdate: (plan, explanation) => {
1757
+ session.emitEvent({
1758
+ type: "PlanUpdated",
1759
+ turnId,
1760
+ explanation,
1761
+ plan
1762
+ });
1763
+ },
1764
+ requestApproval: async (description, command, prefixRule) => {
1765
+ const approvalId = `appr_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1766
+ return session.requestApproval({
1767
+ approvalId,
1768
+ turnId,
1769
+ toolName: toolCall.name,
1770
+ description,
1771
+ command
1772
+ });
1773
+ },
1774
+ requestInput: async (question, options) => {
1775
+ const questionId = `quest_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1776
+ return session.requestUserQuestion({
1777
+ questionId,
1778
+ turnId,
1779
+ question,
1780
+ options
1781
+ });
1782
+ }
1783
+ });
1784
+ const functionOutputItem = {
1785
+ id: `out_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1786
+ type: "function_call_output",
1787
+ callId: toolCall.callId,
1788
+ output: toolResult.output,
1789
+ isError: toolResult.isError,
1790
+ createdAt: Date.now()
1791
+ };
1792
+ if (!toolResult.isError) {
1793
+ if (toolCall.name === "apply_patch" || toolCall.name === "write_file") {
1794
+ const p = String(toolCall.arguments?.path || "");
1795
+ if (p) {
1796
+ modifiedFiles.add(p);
1797
+ hasRunVerification = false;
1798
+ }
1799
+ }
1800
+ }
1801
+ session.addHistoryItem(functionOutputItem);
1802
+ session.emitEvent({
1803
+ type: "ToolCallFinished",
1804
+ turnId,
1805
+ toolName: toolCall.name,
1806
+ output: toolResult.output,
1807
+ isError: toolResult.isError
1808
+ });
1809
+ session.emitEvent({
1810
+ type: "ItemCompleted",
1811
+ turnId,
1812
+ item: functionOutputItem
1813
+ });
1814
+ }
1815
+ continue;
1816
+ }
1817
+ if (session.autoVerification && modifiedFiles.size > 0 && !hasRunVerification && iteration < turnContext.maxIterations) {
1818
+ const verifier = new AutoVerifier({
1819
+ cwd,
1820
+ customCommand: session.autoVerificationCommand
1821
+ });
1822
+ const command = verifier.resolveVerificationCommand();
1823
+ if (command) {
1824
+ session.emitEvent({
1825
+ type: "VerificationStarted",
1826
+ turnId,
1827
+ command,
1828
+ modifiedFiles: Array.from(modifiedFiles)
1829
+ });
1830
+ const vResult = verifier.verify(Array.from(modifiedFiles));
1831
+ session.emitEvent({
1832
+ type: "VerificationCompleted",
1833
+ turnId,
1834
+ command: vResult.command,
1835
+ success: vResult.success,
1836
+ output: vResult.output,
1837
+ durationMs: vResult.durationMs
1838
+ });
1839
+ if (!vResult.success) {
1840
+ if (selfHealingAttempts < session.maxSelfHealingAttempts) {
1841
+ selfHealingAttempts++;
1842
+ session.emitEvent({
1843
+ type: "SelfHealingStarted",
1844
+ turnId,
1845
+ attempt: selfHealingAttempts,
1846
+ maxAttempts: session.maxSelfHealingAttempts,
1847
+ command: vResult.command,
1848
+ error: vResult.output
1849
+ });
1850
+ const feedbackMsg = `[Automated Self-Verification Failure]
1851
+ Verification command '${vResult.command}' failed with exit code ${vResult.exitCode}.
1852
+
1853
+ Error trace / compiler output:
1854
+ ${vResult.output}
1855
+
1856
+ Modified file(s) in this turn: ${Array.from(modifiedFiles).join(", ")}
1857
+
1858
+ Self-Healing Directive (Attempt ${selfHealingAttempts} of ${session.maxSelfHealingAttempts}):
1859
+ 1. Review the error trace above carefully and locate the exact root cause.
1860
+ 2. Formulate and apply the necessary surgical fix using 'apply_patch' or 'write_file'.
1861
+ 3. Do NOT conclude the turn or report to the user until this error is resolved and verification passes cleanly.`;
1862
+ session.addHistoryItem({
1863
+ id: `msg_heal_${Date.now()}`,
1864
+ type: "user_message",
1865
+ content: feedbackMsg,
1866
+ createdAt: Date.now()
1867
+ });
1868
+ continue;
1869
+ } else {
1870
+ session.emitEvent({
1871
+ type: "Warning",
1872
+ message: `Auto-verification failed after ${selfHealingAttempts} self-healing attempts for command: ${vResult.command}`
1873
+ });
1874
+ hasRunVerification = true;
1875
+ }
1876
+ } else {
1877
+ hasRunVerification = true;
1878
+ }
1879
+ }
1880
+ }
1881
+ if (!currentAgentText.trim() && toolCallRequests.length === 0) {
1882
+ if (iteration === 1 && iteration < turnContext.maxIterations) {
1883
+ session.addHistoryItem({
1884
+ id: `msg_nudge_${Date.now()}`,
1885
+ type: "user_message",
1886
+ content: `[Systematic ReAct Nudge]: No tool actions or answers were produced in this iteration.
1887
+ 1. Review the user's objective and determine the immediate next action.
1888
+ 2. If more context is required, invoke an exploration tool ('read_file', 'list_dir', 'grep_search', 'find_files').
1889
+ 3. If ready to answer or implement, call the required mutating tool or deliver your full, concrete response now.`,
1890
+ createdAt: Date.now()
1891
+ });
1892
+ continue;
1893
+ }
1894
+ }
1895
+ break;
1896
+ }
1897
+ const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
1898
+ const maxContextTokens = DEFAULT_MAX_CONTEXT_TOKENS;
1899
+ session.emitEvent({
1900
+ type: "TurnCompleted",
1901
+ turnId,
1902
+ inputTokens: accumulatedInputTokens,
1903
+ outputTokens: accumulatedOutputTokens,
1904
+ totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
1905
+ cachedTokens: accumulatedCachedTokens > 0 ? accumulatedCachedTokens : undefined,
1906
+ contextTokens: totalContextTokens,
1907
+ maxContextTokens
1908
+ });
1909
+ } catch (error) {
1910
+ const isAborted = error instanceof TurnAbortedError || signal.aborted;
1911
+ const message = isAborted ? "Turn was interrupted" : error instanceof Error ? error.message : String(error);
1912
+ session.emitEvent({
1913
+ type: "Error",
1914
+ turnId,
1915
+ message,
1916
+ code: isAborted ? "TURN_ABORTED" : "EXECUTION_ERROR"
1917
+ });
1918
+ } finally {
1919
+ session.clearActiveTurn(turnId);
1920
+ globalEphemeralWorkspace.cleanupTurn(turnId);
1921
+ globalEphemeralWorkspace.cleanRootResidue(turnContext.environment.cwd);
1922
+ }
1923
+ }
1924
+
1925
+ // src/session/turn-input.ts
1926
+ async function handleTurnInput(session, request) {
1927
+ const activeTurn = session.getActiveTurn();
1928
+ if (!activeTurn) {
1929
+ const turnId = `turn_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
1930
+ const turnContext = new TurnContext(turnId, session.model, session.tools, { cwd: session.cwd });
1931
+ session.setActiveTurn(turnContext);
1932
+ runTurn(session, turnContext, request).catch((err) => {
1933
+ session.emitEvent({
1934
+ type: "Error",
1935
+ turnId,
1936
+ message: `Unhandled turn error: ${err instanceof Error ? err.message : String(err)}`
1937
+ });
1938
+ });
1939
+ return {
1940
+ kind: "started",
1941
+ turnId
1942
+ };
1943
+ }
1944
+ session.addHistoryItem({
1945
+ id: `steer_${Date.now()}`,
1946
+ type: "user_message",
1947
+ content: `[Steering Guidance]: ${request.text}`,
1948
+ images: request.images,
1949
+ createdAt: Date.now()
1950
+ });
1951
+ return {
1952
+ kind: "steered",
1953
+ turnId: activeTurn.turnId
1954
+ };
1955
+ }
1956
+
1957
+ // src/session/submission-loop.ts
1958
+ async function submissionLoop(session, queue) {
1959
+ for await (const submission of queue) {
1960
+ const { op } = submission;
1961
+ switch (op.type) {
1962
+ case "TurnInput": {
1963
+ const text = op.request?.text ?? op.prompt ?? (typeof op.request === "string" ? op.request : "");
1964
+ const images = op.request?.images ?? op.images;
1965
+ await handleTurnInput(session, {
1966
+ text,
1967
+ images,
1968
+ clientId: op.request?.clientId,
1969
+ additionalContext: op.request?.additionalContext
1970
+ });
1971
+ break;
1972
+ }
1973
+ case "Interrupt": {
1974
+ session.interrupt();
1975
+ break;
1976
+ }
1977
+ case "ExecApproval": {
1978
+ session.resolveApproval(op.approvalId, op.approved);
1979
+ break;
1980
+ }
1981
+ case "Shutdown": {
1982
+ session.interrupt();
1983
+ session.emitEvent({
1984
+ type: "StatusChanged",
1985
+ status: "terminated"
1986
+ });
1987
+ return;
1988
+ }
1989
+ }
1990
+ }
1991
+ }
1992
+
1993
+ // src/security/exec-policy.ts
1994
+ class ExecPolicy {
1995
+ rules = [];
1996
+ mode = "auto";
1997
+ constructor(initialMode = "auto") {
1998
+ this.mode = initialMode;
1999
+ this.initDefaultRules();
2000
+ }
2001
+ getMode() {
2002
+ return this.mode;
2003
+ }
2004
+ setMode(mode) {
2005
+ this.mode = mode;
2006
+ }
2007
+ initDefaultRules() {
2008
+ this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
2009
+ this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
2010
+ this.addRule(/^(bun\s+(test|--version|-v)|npm\s+(test|--version|-v)|node\s+-v)\b/i, "allow", "Testing & runtime check");
2011
+ this.addRule(/^(rm|del|rmdir|format|mkfs)\b/i, "prompt", "Destructive file removal");
2012
+ this.addRule(/^(git\s+(push|reset\s+--hard|clean\s+-fd|rebase))\b/i, "prompt", "Destructive git operation");
2013
+ this.addRule(/^(curl|wget|fetch|ssh|scp|ftp)\b/i, "prompt", "Network / remote transfer");
2014
+ }
2015
+ addRule(pattern, decision, description) {
2016
+ this.rules.unshift({ pattern, decision, description });
2017
+ }
2018
+ shouldPromptFileEdit(filePath) {
2019
+ if (this.mode === "plan") {
2020
+ return {
2021
+ prompt: true,
2022
+ isPlanBlocked: true,
2023
+ reason: `[Plan Mode Gate] Approval required to mutate '${filePath || "file"}' and proceed with implementation.`
2024
+ };
2025
+ }
2026
+ if (this.mode === "manual") {
2027
+ return {
2028
+ prompt: true,
2029
+ reason: `Manual mode requires approval to modify '${filePath || "file"}'`
2030
+ };
2031
+ }
2032
+ return { prompt: false };
2033
+ }
2034
+ evaluate(command) {
2035
+ const trimmed = command.trim();
2036
+ if (this.mode === "plan") {
2037
+ 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);
2038
+ if (isReadOnly) {
2039
+ return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
2040
+ }
2041
+ return {
2042
+ decision: "prompt",
2043
+ reason: `[Plan Mode Gate] Approval required to execute shell command '${trimmed}' in Plan Mode`
2044
+ };
2045
+ }
2046
+ if (this.mode === "manual") {
2047
+ return {
2048
+ decision: "prompt",
2049
+ reason: "Manual mode requires confirmation for all shell commands"
2050
+ };
2051
+ }
2052
+ if (this.mode === "accept-edits") {
2053
+ const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|bun\s+test|npm\s+test)\b/i.test(trimmed);
2054
+ if (isReadOnly) {
2055
+ return { decision: "allow", reason: "Safe read-only command in accept-edits mode" };
2056
+ }
2057
+ return {
2058
+ decision: "prompt",
2059
+ reason: "Accept-edits mode requires approval for active shell commands"
2060
+ };
2061
+ }
2062
+ for (const rule of this.rules) {
2063
+ if (rule.pattern.test(trimmed)) {
2064
+ return {
2065
+ decision: rule.decision,
2066
+ reason: rule.description
2067
+ };
2068
+ }
2069
+ }
2070
+ return {
2071
+ decision: "allow",
2072
+ reason: "Auto mode allows execution"
2073
+ };
2074
+ }
2075
+ }
2076
+
2077
+ // src/session/session.ts
2078
+ class Session {
2079
+ threadId;
2080
+ model;
2081
+ cwd;
2082
+ systemPrompt;
2083
+ modelClient;
2084
+ tools;
2085
+ skillsLoader;
2086
+ memoryStore;
2087
+ mcpManager;
2088
+ execPolicy;
2089
+ collaborationMode = "default";
2090
+ autoVerification;
2091
+ autoVerificationCommand;
2092
+ maxSelfHealingAttempts;
2093
+ get permissionMode() {
2094
+ return this.execPolicy.getMode();
2095
+ }
2096
+ setPermissionMode(mode) {
2097
+ this.execPolicy.setMode(mode);
2098
+ if (mode === "plan") {
2099
+ this.collaborationMode = "plan";
2100
+ } else if (this.collaborationMode === "plan") {
2101
+ this.collaborationMode = "default";
2102
+ }
2103
+ }
2104
+ history = [];
2105
+ activeTurn = null;
2106
+ status = "idle";
2107
+ eventListeners = [];
2108
+ pendingApprovals = new Map;
2109
+ pendingUserQuestions = new Map;
2110
+ submissionResolvers = [];
2111
+ submissionQueue = [];
2112
+ isTerminated = false;
2113
+ constructor(options = {}) {
2114
+ this.threadId = options.threadId || `thread_${Date.now()}`;
2115
+ this.model = options.model || "gpt-4o";
2116
+ this.cwd = options.cwd || process.cwd();
2117
+ this.systemPrompt = options.systemPrompt || "";
2118
+ this.modelClient = options.modelClient || new ModelClient;
2119
+ this.tools = options.tools || new ToolRouter;
2120
+ this.skillsLoader = options.skillsLoader;
2121
+ this.memoryStore = options.memoryStore;
2122
+ this.mcpManager = options.mcpManager;
2123
+ this.execPolicy = options.execPolicy || new ExecPolicy;
2124
+ this.collaborationMode = options.collaborationMode || "default";
2125
+ this.autoVerification = options.autoVerification ?? (process.env.PIKAA_AUTO_VERIFY !== "0" && process.env.PIKAA_AUTO_VERIFY !== "false");
2126
+ this.autoVerificationCommand = options.autoVerificationCommand;
2127
+ this.maxSelfHealingAttempts = options.maxSelfHealingAttempts ?? 3;
2128
+ this.history = options.initialHistory ? [...options.initialHistory] : [];
2129
+ if (options.onEvent) {
2130
+ this.eventListeners.push(options.onEvent);
2131
+ }
2132
+ this.startSubmissionLoop();
2133
+ this.emitEvent({
2134
+ type: "SessionConfigured",
2135
+ threadId: this.threadId,
2136
+ model: this.model
2137
+ });
2138
+ }
2139
+ onEvent(listener) {
2140
+ this.eventListeners.push(listener);
2141
+ return () => {
2142
+ this.eventListeners = this.eventListeners.filter((l) => l !== listener);
2143
+ };
2144
+ }
2145
+ emitEvent(msg) {
2146
+ if (msg.type === "StatusChanged") {
2147
+ this.status = msg.status;
2148
+ }
2149
+ const event = {
2150
+ id: `evt_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
2151
+ timestamp: Date.now(),
2152
+ msg
2153
+ };
2154
+ for (const listener of this.eventListeners) {
2155
+ try {
2156
+ listener(event);
2157
+ } catch (err) {
2158
+ console.error("Error in event listener:", err);
2159
+ }
2160
+ }
2161
+ }
2162
+ getHistory() {
2163
+ return [...this.history];
2164
+ }
2165
+ setHistory(items) {
2166
+ this.history = [...items];
2167
+ }
2168
+ addHistoryItem(item) {
2169
+ this.history.push(item);
2170
+ }
2171
+ getActiveTurn() {
2172
+ return this.activeTurn;
2173
+ }
2174
+ setActiveTurn(turn) {
2175
+ this.activeTurn = turn;
2176
+ this.emitEvent({
2177
+ type: "StatusChanged",
2178
+ status: "running"
2179
+ });
2180
+ }
2181
+ clearActiveTurn(turnId) {
2182
+ if (!turnId || this.activeTurn?.turnId === turnId) {
2183
+ this.activeTurn = null;
2184
+ this.emitEvent({
2185
+ type: "StatusChanged",
2186
+ status: "idle"
2187
+ });
2188
+ }
2189
+ }
2190
+ interrupt() {
2191
+ if (this.activeTurn) {
2192
+ this.activeTurn.abort("Interrupted by user");
2193
+ this.clearActiveTurn();
2194
+ this.emitEvent({
2195
+ type: "StatusChanged",
2196
+ status: "interrupted"
2197
+ });
2198
+ }
2199
+ }
2200
+ requestApproval(params) {
2201
+ this.emitEvent({
2202
+ type: "ApprovalRequired",
2203
+ approvalId: params.approvalId,
2204
+ turnId: params.turnId,
2205
+ toolName: params.toolName,
2206
+ description: params.description,
2207
+ command: params.command
2208
+ });
2209
+ this.emitEvent({
2210
+ type: "StatusChanged",
2211
+ status: "waiting_approval"
2212
+ });
2213
+ return new Promise((resolve) => {
2214
+ this.pendingApprovals.set(params.approvalId, (approved) => {
2215
+ this.emitEvent({
2216
+ type: "StatusChanged",
2217
+ status: "running"
2218
+ });
2219
+ resolve(approved);
2220
+ });
2221
+ });
2222
+ }
2223
+ resolveApproval(approvalId, approved) {
2224
+ const resolver = this.pendingApprovals.get(approvalId);
2225
+ if (resolver) {
2226
+ this.pendingApprovals.delete(approvalId);
2227
+ resolver(approved);
2228
+ }
2229
+ }
2230
+ requestUserQuestion(params) {
2231
+ this.emitEvent({
2232
+ type: "UserQuestionRequired",
2233
+ questionId: params.questionId,
2234
+ turnId: params.turnId,
2235
+ question: params.question,
2236
+ options: params.options
2237
+ });
2238
+ this.emitEvent({
2239
+ type: "StatusChanged",
2240
+ status: "waiting_user_input"
2241
+ });
2242
+ return new Promise((resolve) => {
2243
+ this.pendingUserQuestions.set(params.questionId, (answer) => {
2244
+ this.emitEvent({
2245
+ type: "StatusChanged",
2246
+ status: "running"
2247
+ });
2248
+ resolve(answer);
2249
+ });
2250
+ });
2251
+ }
2252
+ resolveUserQuestion(questionId, answer) {
2253
+ const resolver = this.pendingUserQuestions.get(questionId);
2254
+ if (resolver) {
2255
+ this.pendingUserQuestions.delete(questionId);
2256
+ resolver(answer);
2257
+ }
2258
+ }
2259
+ async submit(op) {
2260
+ const subId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
2261
+ const submission = {
2262
+ id: subId,
2263
+ op,
2264
+ createdAt: Date.now()
2265
+ };
2266
+ const resolver = this.submissionResolvers.shift();
2267
+ if (resolver) {
2268
+ resolver(submission);
2269
+ } else {
2270
+ this.submissionQueue.push(submission);
2271
+ }
2272
+ return subId;
2273
+ }
2274
+ async prompt(text, images) {
2275
+ return handleTurnInput(this, { text, images });
2276
+ }
2277
+ async promptAndWait(text, images, timeoutMs = 30000) {
2278
+ return new Promise((resolve, reject) => {
2279
+ const timer = setTimeout(() => {
2280
+ unsub();
2281
+ reject(new Error(`Turn timed out after ${timeoutMs}ms`));
2282
+ }, timeoutMs);
2283
+ const unsub = this.onEvent((event) => {
2284
+ if (event.msg.type === "TurnCompleted") {
2285
+ clearTimeout(timer);
2286
+ unsub();
2287
+ resolve();
2288
+ } else if (event.msg.type === "Error") {
2289
+ clearTimeout(timer);
2290
+ unsub();
2291
+ reject(new Error(event.msg.message));
2292
+ }
2293
+ });
2294
+ this.prompt(text, images).catch((err) => {
2295
+ clearTimeout(timer);
2296
+ unsub();
2297
+ reject(err);
2298
+ });
2299
+ });
2300
+ }
2301
+ async* createSubmissionIterator() {
2302
+ while (!this.isTerminated) {
2303
+ if (this.submissionQueue.length > 0) {
2304
+ yield this.submissionQueue.shift();
2305
+ } else {
2306
+ const nextSub = await new Promise((resolve) => {
2307
+ this.submissionResolvers.push(resolve);
2308
+ });
2309
+ yield nextSub;
2310
+ }
2311
+ }
2312
+ }
2313
+ startSubmissionLoop() {
2314
+ const iterator = this.createSubmissionIterator();
2315
+ submissionLoop(this, iterator).catch((err) => {
2316
+ console.error("Submission loop terminated with error:", err);
2317
+ });
2318
+ }
2319
+ }
2320
+ // src/tools/handlers/apply-patch.ts
2321
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
2322
+ import { resolve as resolve5, dirname as dirname3 } from "path";
2323
+ import { mkdirSync as mkdirSync4 } from "fs";
2324
+ var applyPatchTool = {
2325
+ name: "apply_patch",
2326
+ description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
2327
+ parameters: {
2328
+ type: "object",
2329
+ properties: {
2330
+ path: {
2331
+ type: "string",
2332
+ description: "Relative or absolute path to the target file"
2333
+ },
2334
+ targetContent: {
2335
+ type: "string",
2336
+ description: "The exact block of code in the file to be replaced. For creating a new file, leave this empty."
2337
+ },
2338
+ replacementContent: {
2339
+ type: "string",
2340
+ description: "The new code content to replace the targetContent with."
2341
+ }
2342
+ },
2343
+ required: ["path", "replacementContent"]
2344
+ },
2345
+ async execute(args, ctx) {
2346
+ const rawPath = String(args.path || "");
2347
+ if (!rawPath) {
2348
+ return { output: "Error: 'path' parameter is required", isError: true };
2349
+ }
2350
+ const filePath = resolve5(ctx.cwd, rawPath);
2351
+ const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
2352
+ const replacementContent = String(args.replacementContent ?? "");
2353
+ if (ctx.execPolicy) {
2354
+ const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
2355
+ if (evalResult.prompt && ctx.requestApproval) {
2356
+ const approval = await ctx.requestApproval(evalResult.reason || `Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
2357
+ const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
2358
+ if (!allowed) {
2359
+ return {
2360
+ output: `[Plan Mode Gate]: File modification declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
2361
+ isError: true
2362
+ };
2363
+ }
2364
+ } else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
2365
+ return {
2366
+ output: `[Plan Mode Gate]: Cannot mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
2367
+ isError: true
2368
+ };
2369
+ }
2370
+ }
2371
+ if (!existsSync8(filePath)) {
2372
+ if (targetContent) {
2373
+ return {
2374
+ output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.
2375
+ [Systematic Error Recovery Checklist]:
2376
+ 1. Root Cause: Trying to patch a non-existent file with targetContent.
2377
+ 2. Fix: For creating new files, leave 'targetContent' empty and provide full contents in 'replacementContent'.
2378
+ 3. Alternatively, check if the file path '${rawPath}' was mistyped.`,
2379
+ isError: true
2380
+ };
2381
+ }
2382
+ try {
2383
+ mkdirSync4(dirname3(filePath), { recursive: true });
2384
+ writeFileSync2(filePath, replacementContent, "utf8");
2385
+ return { output: `Successfully created new file '${rawPath}'` };
2386
+ } catch (err) {
2387
+ return {
2388
+ output: `Failed to create file '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
2389
+ isError: true
2390
+ };
2391
+ }
2392
+ }
2393
+ try {
2394
+ const originalFileContent = readFileSync6(filePath, "utf8");
2395
+ if (!targetContent) {
2396
+ return {
2397
+ output: `Error: File '${rawPath}' already exists, but targetContent was empty.
2398
+ [Systematic Error Recovery Checklist]:
2399
+ 1. Root Cause: An existing file requires targetContent to specify which lines to replace.
2400
+ 2. Fix: Call 'read_file' on '${rawPath}', extract the exact target lines, and provide them in 'targetContent'.
2401
+ 3. To overwrite the whole file, use the 'write_file' tool instead.`,
2402
+ isError: true
2403
+ };
2404
+ }
2405
+ const firstIndex = originalFileContent.indexOf(targetContent);
2406
+ if (firstIndex === -1) {
2407
+ return {
2408
+ output: `Error: targetContent was not found in '${rawPath}'.
2409
+ [Systematic Error Recovery Checklist]:
2410
+ 1. Root Cause: The snippet in targetContent does not match the actual file content (differences in whitespace, indentation, line endings, or prior edits).
2411
+ 2. Action: Call 'read_file' on '${rawPath}' to inspect current exact lines and indentation.
2412
+ 3. Fix: Provide the exact matching lines (including leading spaces) or wider context, then retry 'apply_patch'.`,
2413
+ isError: true
2414
+ };
2415
+ }
2416
+ const secondIndex = originalFileContent.indexOf(targetContent, firstIndex + 1);
2417
+ if (secondIndex !== -1) {
2418
+ return {
2419
+ output: `Error: targetContent matched multiple locations in '${rawPath}'.
2420
+ [Systematic Error Recovery Checklist]:
2421
+ 1. Root Cause: targetContent is ambiguous and occurs multiple times in the file.
2422
+ 2. Action: Include 2-3 additional surrounding lines (before or after the target block) to make the target snippet uniquely identifiable.
2423
+ 3. Fix: Re-run 'apply_patch' with the extended unique block.`,
2424
+ isError: true
2425
+ };
2426
+ }
2427
+ const newFileContent = originalFileContent.slice(0, firstIndex) + replacementContent + originalFileContent.slice(firstIndex + targetContent.length);
2428
+ writeFileSync2(filePath, newFileContent, "utf8");
2429
+ return {
2430
+ output: `Successfully applied patch to '${rawPath}'`
2431
+ };
2432
+ } catch (err) {
2433
+ return {
2434
+ output: `Failed to apply patch to '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
2435
+ isError: true
2436
+ };
2437
+ }
2438
+ }
2439
+ };
2440
+ // src/security/kernel/windows.ts
1867
2441
  import { dlopen, FFIType } from "bun:ffi";
1868
2442
 
1869
2443
  class WindowsSandbox {
@@ -1950,7 +2524,7 @@ class WindowsSandbox {
1950
2524
  }
1951
2525
 
1952
2526
  // src/security/kernel/linux.ts
1953
- import { existsSync as existsSync7 } from "fs";
2527
+ import { existsSync as existsSync9 } from "fs";
1954
2528
 
1955
2529
  class LinuxSandbox {
1956
2530
  hasBwrap = false;
@@ -1961,7 +2535,7 @@ class LinuxSandbox {
1961
2535
  if (process.platform !== "linux") {
1962
2536
  return;
1963
2537
  }
1964
- this.hasBwrap = existsSync7("/usr/bin/bwrap") || existsSync7("/bin/bwrap") || existsSync7("/usr/local/bin/bwrap");
2538
+ this.hasBwrap = existsSync9("/usr/bin/bwrap") || existsSync9("/bin/bwrap") || existsSync9("/usr/local/bin/bwrap");
1965
2539
  }
1966
2540
  wrapCommand(cmd, profile) {
1967
2541
  if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
@@ -1995,7 +2569,7 @@ class LinuxSandbox {
1995
2569
  }
1996
2570
 
1997
2571
  // src/security/kernel/macos.ts
1998
- import { existsSync as existsSync8 } from "fs";
2572
+ import { existsSync as existsSync10 } from "fs";
1999
2573
 
2000
2574
  class MacOSSandbox {
2001
2575
  hasSandboxExec = false;
@@ -2006,7 +2580,7 @@ class MacOSSandbox {
2006
2580
  if (process.platform !== "darwin") {
2007
2581
  return;
2008
2582
  }
2009
- this.hasSandboxExec = existsSync8("/usr/bin/sandbox-exec");
2583
+ this.hasSandboxExec = existsSync10("/usr/bin/sandbox-exec");
2010
2584
  }
2011
2585
  generateProfile(profile) {
2012
2586
  const rules = [
@@ -2116,7 +2690,7 @@ var globalKernelSandbox = new KernelSandboxManager;
2116
2690
 
2117
2691
  // src/storage/prefix-rules-store.ts
2118
2692
  import { Database } from "bun:sqlite";
2119
- import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
2693
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
2120
2694
  import { dirname as dirname4, resolve as resolve7 } from "path";
2121
2695
  class PrefixRulesStore {
2122
2696
  db;
@@ -2127,7 +2701,7 @@ class PrefixRulesStore {
2127
2701
  const effectivePath = dbOrPath || getPrefixRulesDbPath();
2128
2702
  if (effectivePath !== ":memory:") {
2129
2703
  const dir = dirname4(effectivePath);
2130
- if (!existsSync9(dir)) {
2704
+ if (!existsSync11(dir)) {
2131
2705
  mkdirSync5(dir, { recursive: true });
2132
2706
  }
2133
2707
  }
@@ -2398,31 +2972,118 @@ ${result.stderr.trim()}`);
2398
2972
  }
2399
2973
  var shellTool = createShellTool();
2400
2974
  // src/tools/handlers/file-ops.ts
2401
- import { readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync10, statSync as statSync3, mkdirSync as mkdirSync6 } from "fs";
2975
+ import { readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync12, statSync as statSync4, mkdirSync as mkdirSync6 } from "fs";
2402
2976
  import { resolve as resolve8, dirname as dirname5 } from "path";
2977
+ var DEFAULT_MAX_UNPAGINATED_LINES = 250;
2403
2978
  var readFileTool = {
2404
2979
  name: "read_file",
2405
- description: "Read the full text content of a file.",
2980
+ description: "Read file content with surgical line-range support. Supports start_line and end_line to inspect specific sections of large files without exhausting token context.",
2406
2981
  parameters: {
2407
2982
  type: "object",
2408
2983
  properties: {
2409
- path: { type: "string", description: "Relative or absolute path to the file." }
2984
+ path: {
2985
+ type: "string",
2986
+ description: "Relative or absolute path to the file."
2987
+ },
2988
+ start_line: {
2989
+ type: "number",
2990
+ description: "Optional 1-indexed line number to start reading from (e.g. 120)."
2991
+ },
2992
+ end_line: {
2993
+ type: "number",
2994
+ description: "Optional 1-indexed line number to end reading at, inclusive (e.g. 180)."
2995
+ },
2996
+ offset: {
2997
+ type: "number",
2998
+ description: "Alias for start_line (1-indexed)."
2999
+ },
3000
+ limit: {
3001
+ type: "number",
3002
+ description: "Maximum number of lines to read."
3003
+ },
3004
+ line_numbers: {
3005
+ type: "boolean",
3006
+ description: "Whether to include line number prefixes ('<line>: <content>'). Defaults to true for range reads."
3007
+ }
2410
3008
  },
2411
3009
  required: ["path"]
2412
3010
  },
2413
3011
  async execute(args, ctx) {
2414
- const filePath = resolve8(ctx.cwd, String(args.path || ""));
2415
- if (!existsSync10(filePath)) {
2416
- return { output: `Error: File not found: '${args.path}'`, isError: true };
3012
+ const rawPath = String(args.path || "");
3013
+ const filePath = resolve8(ctx.cwd, rawPath);
3014
+ if (!existsSync12(filePath)) {
3015
+ return { output: `Error: File not found: '${rawPath}'`, isError: true };
2417
3016
  }
2418
3017
  try {
2419
- const content = readFileSync5(filePath, "utf8");
2420
- return { output: content };
3018
+ const content = readFileSync7(filePath, "utf8");
3019
+ const lines = content.split(/\r?\n/);
3020
+ const totalLines = lines.length;
3021
+ const hasRange = args.start_line !== undefined || args.end_line !== undefined || args.startLine !== undefined || args.endLine !== undefined || args.offset !== undefined || args.limit !== undefined;
3022
+ if (!hasRange) {
3023
+ if (args.line_numbers === true) {
3024
+ const formatted = lines.map((l, idx) => `${idx + 1}: ${l}`).join(`
3025
+ `);
3026
+ return { output: formatted };
3027
+ }
3028
+ if (totalLines <= DEFAULT_MAX_UNPAGINATED_LINES) {
3029
+ return { output: content };
3030
+ }
3031
+ const truncated = lines.slice(0, DEFAULT_MAX_UNPAGINATED_LINES);
3032
+ const formatted = truncated.map((l, idx) => `${idx + 1}: ${l}`).join(`
3033
+ `);
3034
+ return {
3035
+ output: `[Showing lines 1 to ${DEFAULT_MAX_UNPAGINATED_LINES} of ${totalLines} in '${rawPath}']
3036
+ ${formatted}
3037
+
3038
+ [Truncated: ${totalLines - DEFAULT_MAX_UNPAGINATED_LINES} more lines. Use start_line=${DEFAULT_MAX_UNPAGINATED_LINES + 1} to continue reading.]`
3039
+ };
3040
+ }
3041
+ const startArg = args.start_line ?? args.startLine ?? args.offset;
3042
+ const start = Math.max(1, typeof startArg === "number" ? Math.floor(startArg) : 1);
3043
+ let end;
3044
+ const endArg = args.end_line ?? args.endLine;
3045
+ if (typeof endArg === "number") {
3046
+ end = Math.min(totalLines, Math.floor(endArg));
3047
+ } else if (typeof args.limit === "number") {
3048
+ end = Math.min(totalLines, start + Math.floor(args.limit) - 1);
3049
+ } else {
3050
+ end = Math.min(totalLines, start + DEFAULT_MAX_UNPAGINATED_LINES - 1);
3051
+ }
3052
+ if (start > totalLines) {
3053
+ return {
3054
+ output: `Error: start_line (${start}) exceeds total lines in file (${totalLines}).`,
3055
+ isError: true
3056
+ };
3057
+ }
3058
+ if (end < start) {
3059
+ return {
3060
+ output: `Error: end_line (${end}) cannot be less than start_line (${start}).`,
3061
+ isError: true
3062
+ };
3063
+ }
3064
+ const sliced = lines.slice(start - 1, end);
3065
+ const withNums = args.line_numbers !== false;
3066
+ const rendered = withNums ? sliced.map((l, idx) => `${start + idx}: ${l}`).join(`
3067
+ `) : sliced.join(`
3068
+ `);
3069
+ let notice = `[Showing lines ${start} to ${end} of ${totalLines} in '${rawPath}']
3070
+ ${rendered}`;
3071
+ if (end < totalLines) {
3072
+ notice += `
3073
+
3074
+ [File has ${totalLines} lines. To read further, use start_line=${end + 1}.]`;
3075
+ }
3076
+ return { output: notice };
2421
3077
  } catch (err) {
2422
3078
  return { output: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
2423
3079
  }
2424
3080
  }
2425
3081
  };
3082
+ var viewFileTool = {
3083
+ ...readFileTool,
3084
+ name: "view_file",
3085
+ description: "View file content with surgical line-range support. Alias for read_file matching Antigravity & Claude Code conventions."
3086
+ };
2426
3087
  var listDirTool = {
2427
3088
  name: "list_dir",
2428
3089
  description: "List contents of a directory with file names and types.",
@@ -2434,14 +3095,14 @@ var listDirTool = {
2434
3095
  },
2435
3096
  async execute(args, ctx) {
2436
3097
  const dirPath = resolve8(ctx.cwd, String(args.path || "."));
2437
- if (!existsSync10(dirPath)) {
3098
+ if (!existsSync12(dirPath)) {
2438
3099
  return { output: `Error: Directory not found: '${args.path}'`, isError: true };
2439
3100
  }
2440
3101
  try {
2441
- const entries = readdirSync3(dirPath);
3102
+ const entries = readdirSync4(dirPath);
2442
3103
  const formatted = entries.map((entry) => {
2443
3104
  const full = resolve8(dirPath, entry);
2444
- const isDir = statSync3(full).isDirectory();
3105
+ const isDir = statSync4(full).isDirectory();
2445
3106
  return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
2446
3107
  });
2447
3108
  return { output: formatted.join(`
@@ -2614,8 +3275,8 @@ var updatePlanTool = {
2614
3275
  }
2615
3276
  };
2616
3277
  // src/search/engine.ts
2617
- import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4, existsSync as existsSync11 } from "fs";
2618
- import { resolve as resolve9, relative, join as join5, extname } from "path";
3278
+ import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5, existsSync as existsSync13 } from "fs";
3279
+ import { resolve as resolve9, relative, join as join7, extname } from "path";
2619
3280
  var DEFAULT_IGNORE_DIRS = new Set([
2620
3281
  ".git",
2621
3282
  "node_modules",
@@ -2661,7 +3322,7 @@ var BINARY_EXTENSIONS = new Set([
2661
3322
  class FileSearchEngine {
2662
3323
  grep(cwd, options) {
2663
3324
  const searchRoot = resolve9(cwd, options.path || ".");
2664
- if (!existsSync11(searchRoot)) {
3325
+ if (!existsSync13(searchRoot)) {
2665
3326
  return { matches: [], totalMatches: 0, truncated: false };
2666
3327
  }
2667
3328
  const maxResults = options.maxResults || 50;
@@ -2686,7 +3347,7 @@ class FileSearchEngine {
2686
3347
  if (truncated)
2687
3348
  break;
2688
3349
  try {
2689
- const content = readFileSync6(filePath, "utf8");
3350
+ const content = readFileSync8(filePath, "utf8");
2690
3351
  const lines = content.split(`
2691
3352
  `);
2692
3353
  for (let i = 0;i < lines.length; i++) {
@@ -2711,7 +3372,7 @@ class FileSearchEngine {
2711
3372
  }
2712
3373
  findFiles(cwd, options) {
2713
3374
  const searchRoot = resolve9(cwd, options.path || ".");
2714
- if (!existsSync11(searchRoot))
3375
+ if (!existsSync13(searchRoot))
2715
3376
  return [];
2716
3377
  const maxResults = options.maxResults || 100;
2717
3378
  const gitignoreRules = this.loadGitignoreRules(searchRoot);
@@ -2750,10 +3411,10 @@ class FileSearchEngine {
2750
3411
  }
2751
3412
  loadGitignoreRules(root) {
2752
3413
  const rules = new Set;
2753
- const gitignorePath = join5(root, ".gitignore");
2754
- if (existsSync11(gitignorePath)) {
3414
+ const gitignorePath = join7(root, ".gitignore");
3415
+ if (existsSync13(gitignorePath)) {
2755
3416
  try {
2756
- const lines = readFileSync6(gitignorePath, "utf8").split(`
3417
+ const lines = readFileSync8(gitignorePath, "utf8").split(`
2757
3418
  `);
2758
3419
  for (const line of lines) {
2759
3420
  const trimmed = line.trim();
@@ -2768,7 +3429,7 @@ class FileSearchEngine {
2768
3429
  collectFiles(dir, root, gitignoreRules, includePattern) {
2769
3430
  const results = [];
2770
3431
  try {
2771
- const stat = statSync4(dir);
3432
+ const stat = statSync5(dir);
2772
3433
  if (!stat.isDirectory()) {
2773
3434
  if (!this.isBinary(dir)) {
2774
3435
  results.push(dir);
@@ -2782,9 +3443,9 @@ class FileSearchEngine {
2782
3443
  while (queue.length > 0) {
2783
3444
  const currentDir = queue.shift();
2784
3445
  try {
2785
- const entries = readdirSync4(currentDir, { withFileTypes: true });
3446
+ const entries = readdirSync5(currentDir, { withFileTypes: true });
2786
3447
  for (const entry of entries) {
2787
- const fullPath = join5(currentDir, entry.name);
3448
+ const fullPath = join7(currentDir, entry.name);
2788
3449
  const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
2789
3450
  if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
2790
3451
  continue;
@@ -2936,7 +3597,13 @@ function createFileSearchTools(engine = new FileSearchEngine) {
2936
3597
  `) };
2937
3598
  }
2938
3599
  };
2939
- return [grepSearchTool, findFilesTool];
3600
+ const findByNameTool = {
3601
+ name: "find_by_name",
3602
+ description: "Search for files and directories across the workspace matching a name or glob pattern. Alias for find_files matching Antigravity & Claude Code conventions.",
3603
+ parameters: findFilesTool.parameters,
3604
+ execute: findFilesTool.execute
3605
+ };
3606
+ return [grepSearchTool, findFilesTool, findByNameTool];
2940
3607
  }
2941
3608
 
2942
3609
  // src/code-mode/tools-proxy.ts
@@ -3455,6 +4122,7 @@ function createDefaultTools(options = {}) {
3455
4122
  router2.register(applyPatchTool);
3456
4123
  router2.register(shellTool);
3457
4124
  router2.register(readFileTool);
4125
+ router2.register(viewFileTool);
3458
4126
  router2.register(writeFileTool);
3459
4127
  router2.register(listDirTool);
3460
4128
  router2.register(requestUserInputTool);
@@ -3483,8 +4151,8 @@ function createDefaultTools(options = {}) {
3483
4151
  }
3484
4152
 
3485
4153
  // src/agents/roles.ts
3486
- import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "fs";
3487
- import { resolve as resolve10, join as join6 } from "path";
4154
+ import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
4155
+ import { resolve as resolve10, join as join8 } from "path";
3488
4156
 
3489
4157
  class AgentRoleRegistry {
3490
4158
  roles = new Map;
@@ -3569,13 +4237,13 @@ class AgentRoleRegistry {
3569
4237
  }
3570
4238
  loadRolesFromDir(dirPath) {
3571
4239
  const fullPath = resolve10(dirPath);
3572
- if (!existsSync12(fullPath))
4240
+ if (!existsSync14(fullPath))
3573
4241
  return;
3574
- const entries = readdirSync5(fullPath);
4242
+ const entries = readdirSync6(fullPath);
3575
4243
  for (const entry of entries) {
3576
4244
  if (entry.endsWith(".json")) {
3577
4245
  try {
3578
- const content = readFileSync7(join6(fullPath, entry), "utf8");
4246
+ const content = readFileSync9(join8(fullPath, entry), "utf8");
3579
4247
  const parsed = JSON.parse(content);
3580
4248
  if (parsed.name && parsed.systemPrompt) {
3581
4249
  this.registerRole(parsed);
@@ -3628,7 +4296,7 @@ function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
3628
4296
  // src/agents/graph-store.ts
3629
4297
  import { Database as Database2 } from "bun:sqlite";
3630
4298
  import { resolve as resolve11 } from "path";
3631
- import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
4299
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
3632
4300
  class AgentGraphStore {
3633
4301
  db;
3634
4302
  constructor(dbPathOrDb) {
@@ -3638,7 +4306,7 @@ class AgentGraphStore {
3638
4306
  const dbPath = dbPathOrDb || getAgentGraphDbPath();
3639
4307
  if (dbPath !== ":memory:") {
3640
4308
  const dir = resolve11(dbPath, "..");
3641
- if (!existsSync13(dir)) {
4309
+ if (!existsSync15(dir)) {
3642
4310
  mkdirSync7(dir, { recursive: true });
3643
4311
  }
3644
4312
  }
@@ -4063,8 +4731,8 @@ function registerMultiAgentTools(router, spawner) {
4063
4731
  }
4064
4732
 
4065
4733
  // src/mcp/manager.ts
4066
- import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync8 } from "fs";
4067
- import { resolve as resolve12, dirname as dirname6, join as join7 } from "path";
4734
+ import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync8 } from "fs";
4735
+ import { resolve as resolve12, dirname as dirname6, join as join9 } from "path";
4068
4736
 
4069
4737
  // src/mcp/client.ts
4070
4738
  class McpClient {
@@ -4262,7 +4930,7 @@ class McpClient {
4262
4930
  }
4263
4931
 
4264
4932
  // src/mcp/process-killer.ts
4265
- import { spawnSync } from "child_process";
4933
+ import { spawnSync as spawnSync2 } from "child_process";
4266
4934
 
4267
4935
  class GlobalProcessRegistry {
4268
4936
  static trackedProcesses = new Map;
@@ -4272,7 +4940,7 @@ class GlobalProcessRegistry {
4272
4940
  return;
4273
4941
  if (process.platform === "win32") {
4274
4942
  try {
4275
- spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], {
4943
+ spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], {
4276
4944
  stdio: "ignore",
4277
4945
  windowsHide: true
4278
4946
  });
@@ -4680,11 +5348,11 @@ class McpManager {
4680
5348
  }
4681
5349
  async loadConfigFile(filePath) {
4682
5350
  const fullPath = resolve12(filePath);
4683
- if (!existsSync14(fullPath))
5351
+ if (!existsSync16(fullPath))
4684
5352
  return;
4685
5353
  this.loadedConfigFiles.add(fullPath);
4686
5354
  try {
4687
- const content = readFileSync8(fullPath, "utf8");
5355
+ const content = readFileSync10(fullPath, "utf8");
4688
5356
  const parsed = JSON.parse(content);
4689
5357
  if (parsed.mcpServers) {
4690
5358
  await this.loadConfig(parsed);
@@ -4935,13 +5603,13 @@ class McpManager {
4935
5603
  saveServerToConfigFile(filePath, name, config) {
4936
5604
  const fullPath = resolve12(filePath);
4937
5605
  const dir = dirname6(fullPath);
4938
- if (!existsSync14(dir)) {
5606
+ if (!existsSync16(dir)) {
4939
5607
  mkdirSync8(dir, { recursive: true });
4940
5608
  }
4941
5609
  let existing = { mcpServers: {} };
4942
- if (existsSync14(fullPath)) {
5610
+ if (existsSync16(fullPath)) {
4943
5611
  try {
4944
- const content = readFileSync8(fullPath, "utf8");
5612
+ const content = readFileSync10(fullPath, "utf8");
4945
5613
  existing = JSON.parse(content);
4946
5614
  if (!existing.mcpServers)
4947
5615
  existing.mcpServers = {};
@@ -4954,10 +5622,10 @@ class McpManager {
4954
5622
  }
4955
5623
  removeServerFromConfigFile(filePath, name) {
4956
5624
  const fullPath = resolve12(filePath);
4957
- if (!existsSync14(fullPath))
5625
+ if (!existsSync16(fullPath))
4958
5626
  return false;
4959
5627
  try {
4960
- const content = readFileSync8(fullPath, "utf8");
5628
+ const content = readFileSync10(fullPath, "utf8");
4961
5629
  const existing = JSON.parse(content);
4962
5630
  if (existing.mcpServers && existing.mcpServers[name]) {
4963
5631
  delete existing.mcpServers[name];
@@ -4984,11 +5652,11 @@ class McpManager {
4984
5652
  }
4985
5653
  }
4986
5654
  getDefaultConfigFile(cwd = process.cwd()) {
4987
- const workspaceConfig = join7(cwd, ".mcp.json");
4988
- if (existsSync14(workspaceConfig))
5655
+ const workspaceConfig = join9(cwd, ".mcp.json");
5656
+ if (existsSync16(workspaceConfig))
4989
5657
  return workspaceConfig;
4990
- const altConfig = join7(cwd, "mcp_config.json");
4991
- if (existsSync14(altConfig))
5658
+ const altConfig = join9(cwd, "mcp_config.json");
5659
+ if (existsSync16(altConfig))
4992
5660
  return altConfig;
4993
5661
  return workspaceConfig;
4994
5662
  }
@@ -5009,7 +5677,7 @@ class McpManager {
5009
5677
 
5010
5678
  // src/storage/sqlite-store.ts
5011
5679
  import { Database as Database3 } from "bun:sqlite";
5012
- import { existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
5680
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
5013
5681
  import { dirname as dirname7 } from "path";
5014
5682
  class SqliteThreadStore {
5015
5683
  db;
@@ -5017,7 +5685,7 @@ class SqliteThreadStore {
5017
5685
  const effectivePath = dbPath || this.getDefaultDbPath();
5018
5686
  if (effectivePath !== ":memory:") {
5019
5687
  const dir = dirname7(effectivePath);
5020
- if (!existsSync15(dir)) {
5688
+ if (!existsSync17(dir)) {
5021
5689
  mkdirSync9(dir, { recursive: true });
5022
5690
  }
5023
5691
  }
@@ -5273,8 +5941,8 @@ class SessionPersistenceManager {
5273
5941
  }
5274
5942
 
5275
5943
  // src/skills/loader.ts
5276
- import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
5277
- import { resolve as resolve13, join as join8 } from "path";
5944
+ import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync11 } from "fs";
5945
+ import { resolve as resolve13, join as join10 } from "path";
5278
5946
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
5279
5947
 
5280
5948
  class SkillsLoader {
@@ -5344,7 +6012,7 @@ class SkillsLoader {
5344
6012
  resolve13(cwd, "skills")
5345
6013
  ];
5346
6014
  for (const cand of candidates) {
5347
- if (existsSync16(cand) && !roots.includes(cand)) {
6015
+ if (existsSync18(cand) && !roots.includes(cand)) {
5348
6016
  roots.push(cand);
5349
6017
  }
5350
6018
  }
@@ -5353,7 +6021,7 @@ class SkillsLoader {
5353
6021
  roots.push(getGlobalSkillsDir());
5354
6022
  }
5355
6023
  roots.push(...this.customRoots.map((r) => resolve13(r)));
5356
- return roots.filter((r) => existsSync16(r));
6024
+ return roots.filter((r) => existsSync18(r));
5357
6025
  }
5358
6026
  discoverSkills(cwd, options) {
5359
6027
  return this.listSkills(cwd, options);
@@ -5369,12 +6037,12 @@ class SkillsLoader {
5369
6037
  const discovered = new Map;
5370
6038
  for (const root of roots) {
5371
6039
  try {
5372
- const entries = readdirSync6(root, { withFileTypes: true });
6040
+ const entries = readdirSync7(root, { withFileTypes: true });
5373
6041
  for (const entry of entries) {
5374
6042
  if (entry.isDirectory()) {
5375
- const skillDir = join8(root, entry.name);
5376
- const skillFilePath = join8(skillDir, "SKILL.md");
5377
- if (existsSync16(skillFilePath)) {
6043
+ const skillDir = join10(root, entry.name);
6044
+ const skillFilePath = join10(skillDir, "SKILL.md");
6045
+ if (existsSync18(skillFilePath)) {
5378
6046
  const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
5379
6047
  if (meta && !discovered.has(meta.name)) {
5380
6048
  meta.enabled = !this.isSkillDisabled(meta.name);
@@ -5403,7 +6071,7 @@ class SkillsLoader {
5403
6071
  if (!meta)
5404
6072
  return null;
5405
6073
  try {
5406
- const raw = readFileSync9(meta.path, "utf8");
6074
+ const raw = readFileSync11(meta.path, "utf8");
5407
6075
  const { body } = this.extractFrontmatterAndBody(raw);
5408
6076
  return {
5409
6077
  ...meta,
@@ -5415,7 +6083,7 @@ class SkillsLoader {
5415
6083
  }
5416
6084
  parseSkillFrontmatter(filePath, dirName, root, cwd) {
5417
6085
  try {
5418
- const raw = readFileSync9(filePath, "utf8");
6086
+ const raw = readFileSync11(filePath, "utf8");
5419
6087
  const { attributes } = this.extractFrontmatterAndBody(raw);
5420
6088
  let scope = "global";
5421
6089
  const normPath = filePath.toLowerCase().replace(/\\/g, "/");
@@ -5497,8 +6165,8 @@ When tackling complex specialized tasks that match any of these skills, autonomo
5497
6165
  }
5498
6166
 
5499
6167
  // src/skills/installer.ts
5500
- import { existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync5, rmSync as rmSync2 } from "fs";
5501
- import { resolve as resolve14, join as join9 } from "path";
6168
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync5, rmSync as rmSync2 } from "fs";
6169
+ import { resolve as resolve14, join as join11 } from "path";
5502
6170
  async function installSkill(skillName, options) {
5503
6171
  const cleanName = skillName.trim().toLowerCase().replace(/^@/, "");
5504
6172
  if (!cleanName) {
@@ -5533,7 +6201,7 @@ async function installSkill(skillName, options) {
5533
6201
  const scope = options.global ? "global" : "workspace";
5534
6202
  const targetDir = options.global ? resolve14(getGlobalSkillsDir(), cleanName) : resolve14(options.cwd, ".agents", "skills", cleanName);
5535
6203
  mkdirSync10(targetDir, { recursive: true });
5536
- const targetFile = join9(targetDir, "SKILL.md");
6204
+ const targetFile = join11(targetDir, "SKILL.md");
5537
6205
  let fileContent = skillData.content;
5538
6206
  if (!fileContent.startsWith("---")) {
5539
6207
  const desc = skillData.description ? `description: "${skillData.description.replace(/"/g, "\\\"")}"
@@ -5557,7 +6225,7 @@ ${fileContent}`;
5557
6225
  function removeSkill(skillName, options) {
5558
6226
  const cleanName = skillName.trim().toLowerCase().replace(/^@/, "");
5559
6227
  const targetDir = options.global ? resolve14(getGlobalSkillsDir(), cleanName) : resolve14(options.cwd, ".agents", "skills", cleanName);
5560
- if (!existsSync17(targetDir)) {
6228
+ if (!existsSync19(targetDir)) {
5561
6229
  return { success: false, targetDir, removed: false };
5562
6230
  }
5563
6231
  rmSync2(targetDir, { recursive: true, force: true });
@@ -5565,8 +6233,8 @@ function removeSkill(skillName, options) {
5565
6233
  }
5566
6234
 
5567
6235
  // src/memories/store.ts
5568
- import { existsSync as existsSync18, readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync11, readdirSync as readdirSync7 } from "fs";
5569
- import { resolve as resolve15, join as join10, basename, dirname as dirname8 } from "path";
6236
+ import { existsSync as existsSync20, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync11, readdirSync as readdirSync8 } from "fs";
6237
+ import { resolve as resolve15, join as join12, basename as basename2, dirname as dirname8 } from "path";
5570
6238
  import { createHash } from "crypto";
5571
6239
  class MemoryStore {
5572
6240
  globalPath;
@@ -5578,7 +6246,7 @@ class MemoryStore {
5578
6246
  findProjectRoot(cwd) {
5579
6247
  let current = resolve15(cwd);
5580
6248
  while (true) {
5581
- if (existsSync18(join10(current, ".git"))) {
6249
+ if (existsSync20(join12(current, ".git"))) {
5582
6250
  return current;
5583
6251
  }
5584
6252
  const parent = dirname8(current);
@@ -5590,14 +6258,14 @@ class MemoryStore {
5590
6258
  }
5591
6259
  getProjectSlug(cwd) {
5592
6260
  const root = this.findProjectRoot(cwd);
5593
- const folderName = basename(root).toLowerCase().replace(/[^a-z0-9_-]/g, "-") || "project";
6261
+ const folderName = basename2(root).toLowerCase().replace(/[^a-z0-9_-]/g, "-") || "project";
5594
6262
  const hash = createHash("sha256").update(resolve15(root)).digest("hex").slice(0, 6);
5595
6263
  return `${folderName}-${hash}`;
5596
6264
  }
5597
6265
  getProjectMemoryDir(cwd) {
5598
6266
  if (this.customWorkspacePath) {
5599
6267
  const dir = resolve15(this.customWorkspacePath);
5600
- if (!existsSync18(dir)) {
6268
+ if (!existsSync20(dir)) {
5601
6269
  try {
5602
6270
  mkdirSync11(dir, { recursive: true });
5603
6271
  } catch {}
@@ -5605,8 +6273,8 @@ class MemoryStore {
5605
6273
  return dir;
5606
6274
  }
5607
6275
  const slug = this.getProjectSlug(cwd);
5608
- const dir = join10(getProjectsDir(), slug, "memory");
5609
- if (!existsSync18(dir)) {
6276
+ const dir = join12(getProjectsDir(), slug, "memory");
6277
+ if (!existsSync20(dir)) {
5610
6278
  try {
5611
6279
  mkdirSync11(dir, { recursive: true });
5612
6280
  } catch {}
@@ -5614,7 +6282,7 @@ class MemoryStore {
5614
6282
  return dir;
5615
6283
  }
5616
6284
  getMemoryIndexPath(cwd) {
5617
- return join10(this.getProjectMemoryDir(cwd), "MEMORY.md");
6285
+ return join12(this.getProjectMemoryDir(cwd), "MEMORY.md");
5618
6286
  }
5619
6287
  normalizeCategory(raw) {
5620
6288
  const cat = raw.toLowerCase().trim();
@@ -5633,7 +6301,7 @@ class MemoryStore {
5633
6301
  const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
5634
6302
  const memoryDir = this.getProjectMemoryDir(params.cwd);
5635
6303
  const fileName = `${type}_${sanitizedName}.md`;
5636
- const filePath = join10(memoryDir, fileName);
6304
+ const filePath = join12(memoryDir, fileName);
5637
6305
  const nowIso = new Date().toISOString();
5638
6306
  const cleanContent = params.content.trim();
5639
6307
  const desc = (params.description || cleanContent.split(`
@@ -5668,23 +6336,23 @@ class MemoryStore {
5668
6336
  }
5669
6337
  readTopicMemory(topicNameOrFile, cwd) {
5670
6338
  const memoryDir = this.getProjectMemoryDir(cwd);
5671
- let targetPath = join10(memoryDir, topicNameOrFile);
5672
- if (!existsSync18(targetPath)) {
6339
+ let targetPath = join12(memoryDir, topicNameOrFile);
6340
+ if (!existsSync20(targetPath)) {
5673
6341
  if (!topicNameOrFile.endsWith(".md")) {
5674
- targetPath = join10(memoryDir, `${topicNameOrFile}.md`);
6342
+ targetPath = join12(memoryDir, `${topicNameOrFile}.md`);
5675
6343
  }
5676
6344
  }
5677
- if (!existsSync18(targetPath)) {
5678
- const files = readdirSync7(memoryDir);
6345
+ if (!existsSync20(targetPath)) {
6346
+ const files = readdirSync8(memoryDir);
5679
6347
  const match = files.find((f) => f.includes(topicNameOrFile));
5680
6348
  if (match) {
5681
- targetPath = join10(memoryDir, match);
6349
+ targetPath = join12(memoryDir, match);
5682
6350
  } else {
5683
6351
  return null;
5684
6352
  }
5685
6353
  }
5686
6354
  try {
5687
- const raw = readFileSync10(targetPath, "utf8");
6355
+ const raw = readFileSync12(targetPath, "utf8");
5688
6356
  return this.parseTopicFile(raw, targetPath);
5689
6357
  } catch {
5690
6358
  return null;
@@ -5695,7 +6363,7 @@ class MemoryStore {
5695
6363
  `);
5696
6364
  let inFm = false;
5697
6365
  let type = "project";
5698
- let name = basename(filePath, ".md");
6366
+ let name = basename2(filePath, ".md");
5699
6367
  let description;
5700
6368
  let modified = new Date().toISOString();
5701
6369
  const bodyLines = [];
@@ -5739,13 +6407,13 @@ class MemoryStore {
5739
6407
  }
5740
6408
  syncMemoryIndex(cwd) {
5741
6409
  const memoryDir = this.getProjectMemoryDir(cwd);
5742
- const indexPath = join10(memoryDir, "MEMORY.md");
5743
- const files = existsSync18(memoryDir) ? readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
6410
+ const indexPath = join12(memoryDir, "MEMORY.md");
6411
+ const files = existsSync20(memoryDir) ? readdirSync8(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
5744
6412
  const items = [];
5745
6413
  for (const f of files) {
5746
6414
  try {
5747
- const full = join10(memoryDir, f);
5748
- const parsed = this.parseTopicFile(readFileSync10(full, "utf8"), full);
6415
+ const full = join12(memoryDir, f);
6416
+ const parsed = this.parseTopicFile(readFileSync12(full, "utf8"), full);
5749
6417
  items.push({
5750
6418
  type: parsed.type,
5751
6419
  name: parsed.name,
@@ -5771,10 +6439,10 @@ class MemoryStore {
5771
6439
  }
5772
6440
  loadMemoryIndex(cwd) {
5773
6441
  const indexPath = this.getMemoryIndexPath(cwd);
5774
- if (!existsSync18(indexPath))
6442
+ if (!existsSync20(indexPath))
5775
6443
  return "";
5776
6444
  try {
5777
- const raw = readFileSync10(indexPath, "utf8");
6445
+ const raw = readFileSync12(indexPath, "utf8");
5778
6446
  const byteLimit = 25 * 1024;
5779
6447
  const sliced = raw.length > byteLimit ? raw.slice(0, byteLimit) : raw;
5780
6448
  const lines = sliced.split(`
@@ -5787,14 +6455,14 @@ class MemoryStore {
5787
6455
  }
5788
6456
  listProjectMemories(cwd) {
5789
6457
  const memoryDir = this.getProjectMemoryDir(cwd);
5790
- if (!existsSync18(memoryDir))
6458
+ if (!existsSync20(memoryDir))
5791
6459
  return [];
5792
- const files = readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
6460
+ const files = readdirSync8(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
5793
6461
  const list = [];
5794
6462
  for (const f of files) {
5795
6463
  try {
5796
- const full = join10(memoryDir, f);
5797
- list.push(this.parseTopicFile(readFileSync10(full, "utf8"), full));
6464
+ const full = join12(memoryDir, f);
6465
+ list.push(this.parseTopicFile(readFileSync12(full, "utf8"), full));
5798
6466
  } catch {}
5799
6467
  }
5800
6468
  return list;
@@ -5845,8 +6513,8 @@ class MemoryStore {
5845
6513
  }
5846
6514
 
5847
6515
  // src/worktree/manager.ts
5848
- import { resolve as resolve17, join as join11 } from "path";
5849
- import { existsSync as existsSync19, mkdirSync as mkdirSync12, writeFileSync as writeFileSync7, readFileSync as readFileSync11 } from "fs";
6516
+ import { resolve as resolve17, join as join13 } from "path";
6517
+ import { existsSync as existsSync21, mkdirSync as mkdirSync12, writeFileSync as writeFileSync7, readFileSync as readFileSync13 } from "fs";
5850
6518
 
5851
6519
  // src/worktree/git.ts
5852
6520
  import { resolve as resolve16 } from "path";
@@ -5996,7 +6664,7 @@ class WorktreeManager {
5996
6664
  const branchName = options.branch || `groupy/${taskId}`;
5997
6665
  const targetDir = options.worktreePath || (this.baseStorageDir ? resolve17(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve17(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
5998
6666
  const worktreeParent = resolve17(targetDir, "..");
5999
- if (!existsSync19(worktreeParent)) {
6667
+ if (!existsSync21(worktreeParent)) {
6000
6668
  mkdirSync12(worktreeParent, { recursive: true });
6001
6669
  }
6002
6670
  const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
@@ -6004,7 +6672,7 @@ class WorktreeManager {
6004
6672
  if (!result.success) {
6005
6673
  throw new Error(`Failed to create git worktree: ${result.error}`);
6006
6674
  }
6007
- const metaPath = join11(targetDir, "groupy-thread.json");
6675
+ const metaPath = join13(targetDir, "groupy-thread.json");
6008
6676
  try {
6009
6677
  writeFileSync7(metaPath, JSON.stringify({
6010
6678
  version: 1,
@@ -6030,10 +6698,10 @@ class WorktreeManager {
6030
6698
  return [];
6031
6699
  const worktrees = await listWorktreesGit(repoRoot);
6032
6700
  return worktrees.map((wt) => {
6033
- const metaPath = join11(wt.path, "groupy-thread.json");
6034
- if (existsSync19(metaPath)) {
6701
+ const metaPath = join13(wt.path, "groupy-thread.json");
6702
+ if (existsSync21(metaPath)) {
6035
6703
  try {
6036
- const raw = JSON.parse(readFileSync11(metaPath, "utf8"));
6704
+ const raw = JSON.parse(readFileSync13(metaPath, "utf8"));
6037
6705
  return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
6038
6706
  } catch {}
6039
6707
  }
@@ -6729,7 +7397,7 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
6729
7397
  // package.json
6730
7398
  var package_default = {
6731
7399
  name: "@pikaa-ai/pikaa",
6732
- version: "0.3.27",
7400
+ version: "0.3.28",
6733
7401
  description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
6734
7402
  main: "./dist/index.js",
6735
7403
  module: "./dist/index.js",
@@ -6761,14 +7429,14 @@ var package_default = {
6761
7429
  prepublishOnly: "bun run build:js"
6762
7430
  },
6763
7431
  optionalDependencies: {
6764
- "@pikaa-ai/pikaa-linux-x64": "0.3.27",
6765
- "@pikaa-ai/pikaa-linux-x64-musl": "0.3.27",
6766
- "@pikaa-ai/pikaa-linux-arm64": "0.3.27",
6767
- "@pikaa-ai/pikaa-linux-arm64-musl": "0.3.27",
6768
- "@pikaa-ai/pikaa-darwin-x64": "0.3.27",
6769
- "@pikaa-ai/pikaa-darwin-arm64": "0.3.27",
6770
- "@pikaa-ai/pikaa-windows-x64": "0.3.27",
6771
- "@pikaa-ai/pikaa-windows-arm64": "0.3.27"
7432
+ "@pikaa-ai/pikaa-linux-x64": "0.3.28",
7433
+ "@pikaa-ai/pikaa-linux-x64-musl": "0.3.28",
7434
+ "@pikaa-ai/pikaa-linux-arm64": "0.3.28",
7435
+ "@pikaa-ai/pikaa-linux-arm64-musl": "0.3.28",
7436
+ "@pikaa-ai/pikaa-darwin-x64": "0.3.28",
7437
+ "@pikaa-ai/pikaa-darwin-arm64": "0.3.28",
7438
+ "@pikaa-ai/pikaa-windows-x64": "0.3.28",
7439
+ "@pikaa-ai/pikaa-windows-arm64": "0.3.28"
6772
7440
  },
6773
7441
  keywords: [
6774
7442
  "ai",
@@ -7973,900 +8641,537 @@ async function promptToolApproval(params) {
7973
8641
  return;
7974
8642
  }
7975
8643
  if (_str?.toLowerCase() === "n") {
7976
- cleanup("no");
7977
- return;
7978
- }
7979
- if (_str?.toLowerCase() === "a") {
7980
- cleanup("always");
7981
- return;
7982
- }
7983
- };
7984
- unsubscribe = addGlobalKeypressListener(onKeypress);
7985
- render();
7986
- });
7987
- }
7988
- async function promptUserQuestion(params) {
7989
- const { question, options = [] } = params;
7990
- const boxWidth = Math.min(process.stdout.columns ?? 80, 75);
7991
- const border = "\u2500".repeat(Math.max(10, boxWidth - 20));
7992
- console.log(`
7993
- ${style.cyan("\u250C\u2500\u2500")} ${style.bold("AI Question")} ${style.cyan(border)}`);
7994
- const qLines = question.split(`
7995
- `);
7996
- for (const line of qLines) {
7997
- console.log(` ${style.cyan("\u2502")} ${style.bold(line)}`);
7998
- }
7999
- if (options.length > 0) {
8000
- console.log(` ${style.cyan("\u2502")}`);
8001
- options.forEach((opt, idx) => {
8002
- const numTag = style.cyan(`[${idx + 1}]`);
8003
- const isRec = opt.includes("(Recommended)") || opt.includes("(recommended)");
8004
- const optText = isRec ? opt.replace(/\(Recommended\)/i, style.green("(Recommended)")) : opt;
8005
- console.log(` ${style.cyan("\u2502")} ${numTag} ${optText}`);
8006
- });
8007
- }
8008
- console.log(` ${style.cyan("\u2514" + "\u2500".repeat(Math.max(10, boxWidth - 4)))}
8009
- `);
8010
- if (!process.stdin.isTTY || false || !process.stdin.readable) {
8011
- return options[0] || "yes";
8012
- }
8013
- return new Promise((resolve) => {
8014
- const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
8015
- const promptLabel = options.length > 0 ? `Select [1-${options.length}] or type custom response: ` : `Your response: `;
8016
- rl.question(` ${style.bold(promptLabel)}`, (answer) => {
8017
- rl.close();
8018
- const trimmed = answer.trim();
8019
- if (!trimmed) {
8020
- const fallback = options[0] || "";
8021
- console.log(style.dim(` \u21B3 Default: ${fallback || "(empty)"}
8022
- `));
8023
- resolve(fallback);
8024
- return;
8025
- }
8026
- const num = parseInt(trimmed, 10);
8027
- if (!isNaN(num) && num >= 1 && num <= options.length) {
8028
- const picked = options[num - 1];
8029
- console.log(style.green(` \u2714 Selected: ${picked}
8030
- `));
8031
- resolve(picked);
8032
- return;
8033
- }
8034
- if (options.length === 2) {
8035
- if (/^(y|yes)$/i.test(trimmed)) {
8036
- console.log(style.green(` \u2714 Selected: ${options[0]}
8037
- `));
8038
- resolve(options[0]);
8039
- return;
8040
- }
8041
- if (/^(n|no)$/i.test(trimmed)) {
8042
- console.log(style.green(` \u2714 Selected: ${options[1]}
8043
- `));
8044
- resolve(options[1]);
8045
- return;
8046
- }
8047
- }
8048
- console.log(style.green(` \u2714 Answer: ${trimmed}
8049
- `));
8050
- resolve(trimmed);
8051
- });
8052
- });
8053
- }
8054
- async function promptInteractiveList(config) {
8055
- const {
8056
- title,
8057
- items,
8058
- mode = "select",
8059
- defaultIndex = 0,
8060
- maxVisible = 8,
8061
- emptyMessage = "No items available.",
8062
- onToggle,
8063
- onAction,
8064
- customKeyHints
8065
- } = config;
8066
- if (!process.stdin.isTTY || false || !process.stdin.readable) {
8067
- return {
8068
- selectedIndex: defaultIndex,
8069
- selectedItem: items[defaultIndex] || items[0],
8070
- action: "select"
8071
- };
8072
- }
8073
- if (items.length === 0) {
8074
- console.log(`
8075
- ${style.dim(emptyMessage)}
8076
- `);
8077
- return { selectedIndex: -1, action: "close" };
8078
- }
8079
- return new Promise((resolve) => {
8080
- let selectedIndex = Math.max(0, Math.min(defaultIndex, items.length - 1));
8081
- let scrollTop = 0;
8082
- let renderedLines = 0;
8083
- const wasRaw = process.stdin.isRaw ?? false;
8084
- ensureRawMode(true);
8085
- process.stdout.write("\x1B[?25l");
8086
- const BOX_WIDTH = Math.min(process.stdout.columns ?? 80, 76);
8087
- const ensureVisible = () => {
8088
- const visibleCount = Math.min(items.length, maxVisible);
8089
- if (selectedIndex < scrollTop) {
8090
- scrollTop = selectedIndex;
8091
- } else if (selectedIndex >= scrollTop + visibleCount) {
8092
- scrollTop = selectedIndex + 1 - visibleCount;
8093
- }
8094
- if (scrollTop < 0)
8095
- scrollTop = 0;
8096
- const maxScroll = Math.max(0, items.length - visibleCount);
8097
- if (scrollTop > maxScroll)
8098
- scrollTop = maxScroll;
8099
- };
8100
- const render = () => {
8101
- ensureVisible();
8102
- const visibleCount = Math.min(items.length, maxVisible);
8103
- const visibleItems = items.slice(scrollTop, scrollTop + visibleCount);
8104
- const lines = [];
8105
- const headerBorder = "\u2500".repeat(Math.max(10, BOX_WIDTH - title.length - 8));
8106
- lines.push(` \x1B[38;2;140;140;150m\u250C\u2500\u2500\x1B[0m ${style.bold(title)} \x1B[38;2;140;140;150m${headerBorder}\u2510\x1B[0m`);
8107
- for (let i = 0;i < visibleItems.length; i++) {
8108
- const item = visibleItems[i];
8109
- const actualIdx = scrollTop + i;
8110
- const isCurrent = actualIdx === selectedIndex;
8111
- const marker = isCurrent ? "\x1B[38;2;217;119;87m\u276F\x1B[0m" : " ";
8112
- let checkSymbol = "";
8113
- if (mode === "toggle") {
8114
- checkSymbol = item.checked ? "\x1B[38;2;78;169;111m[\u2714 ENABLED]\x1B[0m " : "\x1B[38;2;120;120;125m[\u25CB DISABLED]\x1B[0m";
8115
- } else if (item.checked !== undefined) {
8116
- checkSymbol = item.checked ? "\x1B[38;2;78;169;111m(\u25CF)\x1B[0m " : "\x1B[38;2;120;120;125m(\u25CB)\x1B[0m ";
8117
- }
8118
- const badgeStr = item.badge ? ` ${style.dim(`(${item.badge})`)}` : "";
8119
- const labelStr = isCurrent ? style.bold(item.label) : item.label;
8120
- lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m ${marker} ${checkSymbol} ${labelStr}${badgeStr}`);
8121
- if (item.description) {
8122
- const descIndent = " ".repeat(mode === "toggle" ? 17 : 7);
8123
- const maxDescLen = BOX_WIDTH - descIndent.length - 6;
8124
- const trimmedDesc = item.description.length > maxDescLen ? item.description.slice(0, maxDescLen - 3) + "..." : item.description;
8125
- lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m ${descIndent}${style.dim(trimmedDesc)}`);
8126
- }
8127
- }
8128
- const moreAbove = scrollTop;
8129
- const moreBelow = Math.max(0, items.length - (scrollTop + visibleCount));
8130
- let scrollInfo = "";
8131
- if (moreAbove > 0 && moreBelow > 0) {
8132
- scrollInfo = `[\u2191 ${moreAbove} more \xB7 \u2193 ${moreBelow} more]`;
8133
- } else if (moreBelow > 0) {
8134
- scrollInfo = `[\u2193 ${moreBelow} more below]`;
8135
- } else if (moreAbove > 0) {
8136
- scrollInfo = `[\u2191 ${moreAbove} more above]`;
8137
- }
8138
- const defaultHints = mode === "toggle" ? "\u2191/\u2193: navigate \xB7 Space: toggle \xB7 a: all \xB7 d: none \xB7 Enter/Esc: done" : "\u2191/\u2193: navigate \xB7 Enter: select \xB7 Esc: cancel";
8139
- const hintText = customKeyHints || (scrollInfo ? `${scrollInfo} ${defaultHints}` : defaultHints);
8140
- lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m`);
8141
- lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m ${style.dim(hintText)}`);
8142
- lines.push(` \x1B[38;2;140;140;150m\u2514\u2500\u2500${"\u2500".repeat(Math.max(10, BOX_WIDTH - 6))}\u2518\x1B[0m`);
8143
- let buffer = "";
8144
- if (renderedLines > 0) {
8145
- buffer += `\x1B[${renderedLines}A\r`;
8146
- }
8147
- buffer += lines.map((l) => `\x1B[2K${l}`).join(`
8148
- `) + `
8149
- `;
8150
- if (renderedLines > lines.length) {
8151
- buffer += "\x1B[J";
8152
- }
8153
- process.stdout.write(buffer);
8154
- renderedLines = lines.length;
8155
- };
8156
- let unsubscribe = null;
8157
- const cleanup = (res) => {
8158
- if (renderedLines > 0) {
8159
- process.stdout.write(`\x1B[${renderedLines}A\r\x1B[J\x1B[?25h`);
8160
- renderedLines = 0;
8161
- } else {
8162
- process.stdout.write("\x1B[?25h");
8163
- }
8164
- if (unsubscribe) {
8165
- unsubscribe();
8166
- unsubscribe = null;
8167
- }
8168
- ensureRawMode(wasRaw);
8169
- resolve(res);
8170
- };
8171
- const onKeypress = async (_str, key) => {
8172
- if (!key)
8173
- return;
8174
- if (key.ctrl && key.name === "c" || key.name === "escape" || _str === "q" || _str === "Q") {
8175
- cleanup({
8176
- selectedIndex,
8177
- selectedItem: items[selectedIndex],
8178
- action: "close"
8179
- });
8180
- return;
8181
- }
8182
- if (key.name === "up" || _str === "k") {
8183
- selectedIndex = (selectedIndex - 1 + items.length) % items.length;
8184
- render();
8185
- return;
8186
- }
8187
- if (key.name === "down" || _str === "j") {
8188
- selectedIndex = (selectedIndex + 1) % items.length;
8189
- render();
8190
- return;
8191
- }
8192
- if (_str === " " || _str === "t" || _str === "T") {
8193
- const item = items[selectedIndex];
8194
- if (item) {
8195
- item.checked = !item.checked;
8196
- if (onToggle) {
8197
- await onToggle(item, selectedIndex);
8198
- }
8199
- }
8200
- render();
8201
- return;
8202
- }
8203
- if (key.name === "return" || key.name === "enter") {
8204
- if (mode === "toggle") {
8205
- cleanup({
8206
- selectedIndex,
8207
- selectedItem: items[selectedIndex],
8208
- action: "close"
8209
- });
8210
- } else {
8211
- cleanup({
8212
- selectedIndex,
8213
- selectedItem: items[selectedIndex],
8214
- action: "select"
8215
- });
8216
- }
8217
- return;
8218
- }
8219
- if (_str && onAction) {
8220
- const item = items[selectedIndex];
8221
- const shouldExit = await onAction(_str.toLowerCase(), item, selectedIndex);
8222
- if (shouldExit) {
8223
- cleanup({
8224
- selectedIndex,
8225
- selectedItem: item,
8226
- action: "custom",
8227
- keyName: _str.toLowerCase()
8228
- });
8229
- return;
8230
- }
8231
- render();
8644
+ cleanup("no");
8645
+ return;
8646
+ }
8647
+ if (_str?.toLowerCase() === "a") {
8648
+ cleanup("always");
8649
+ return;
8232
8650
  }
8233
8651
  };
8234
8652
  unsubscribe = addGlobalKeypressListener(onKeypress);
8235
8653
  render();
8236
8654
  });
8237
8655
  }
8238
-
8239
- // src/security/scanner.ts
8240
- import { existsSync as existsSync20, readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
8241
- import { join as join12, relative as relative2, resolve as resolve18 } from "path";
8242
- var SECURITY_RULES = [
8243
- {
8244
- id: "SEC-001",
8245
- category: "Secrets Leakage",
8246
- severity: "CRITICAL",
8247
- pattern: /sk-(?:proj-)?[A-Za-z0-9-_]{20,}/,
8248
- description: "Hardcoded OpenAI API key detected in source code.",
8249
- recommendation: "Store API keys in environment variables (e.g. process.env.OPENAI_API_KEY) or a secure vault."
8250
- },
8251
- {
8252
- id: "SEC-002",
8253
- category: "Secrets Leakage",
8254
- severity: "CRITICAL",
8255
- pattern: /AKIA[0-9A-Z]{16}/,
8256
- description: "Exposed AWS Access Key ID found in source code.",
8257
- recommendation: "Rotate the exposed key immediately and use IAM Roles or AWS environment credentials."
8258
- },
8259
- {
8260
- id: "SEC-003",
8261
- category: "Secrets Leakage",
8262
- severity: "CRITICAL",
8263
- pattern: /ghp_[0-9a-zA-Z]{36}/,
8264
- description: "Exposed GitHub Personal Access Token (PAT) detected.",
8265
- recommendation: "Revoke the token and inject credentials via GitHub Actions Secrets or environment variables."
8266
- },
8267
- {
8268
- id: "SEC-004",
8269
- category: "Secrets Leakage",
8270
- severity: "CRITICAL",
8271
- pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/,
8272
- description: "Unencrypted private cryptographic key committed to source repository.",
8273
- recommendation: "Remove private key from git history and manage certificates through a secret manager."
8274
- },
8275
- {
8276
- id: "SEC-005",
8277
- category: "Secrets Leakage",
8278
- severity: "HIGH",
8279
- pattern: /(?:postgres|mysql|mongodb(?:\+srv)?):\/\/[a-zA-Z0-9_.-]+:[^@\s"']+@[a-zA-Z0-9_.-]+/i,
8280
- description: "Hardcoded database connection string containing plaintext credentials.",
8281
- recommendation: "Extract connection URI into DATABASE_URL environment variable."
8282
- },
8283
- {
8284
- id: "SEC-010",
8285
- category: "Code Injection",
8286
- severity: "HIGH",
8287
- pattern: /\beval\s*\(/,
8288
- description: "Use of dangerous `eval()` function permits arbitrary code execution.",
8289
- recommendation: "Avoid eval(). Use structured JSON.parse() or dedicated domain-specific parsers.",
8290
- fileExtensions: [".js", ".ts", ".jsx", ".tsx", ".py"]
8291
- },
8292
- {
8293
- id: "SEC-011",
8294
- category: "Command Injection",
8295
- severity: "HIGH",
8296
- pattern: /(?:child_process|cp)\.exec\s*\([^,)]*\+/,
8297
- description: "Dynamic string concatenation in `child_process.exec()` creates command injection vectors.",
8298
- recommendation: "Use `execFile` or `spawn` with an array of arguments rather than executing raw shell strings.",
8299
- fileExtensions: [".js", ".ts"]
8300
- },
8301
- {
8302
- id: "SEC-020",
8303
- category: "Broken Authentication",
8304
- severity: "MEDIUM",
8305
- pattern: /jwt\.(?:sign|verify)\s*\([^,]+,\s*["'](?:secret|test|123456|dev|password)["']\s*\)/i,
8306
- description: "Weak or hardcoded JWT secret key used in token signing/verification.",
8307
- recommendation: "Use a high-entropy secret (at least 256 bits) loaded securely from environment variables.",
8308
- fileExtensions: [".js", ".ts", ".py"]
8309
- },
8310
- {
8311
- id: "SEC-021",
8312
- category: "Broken Authentication",
8313
- severity: "MEDIUM",
8314
- pattern: /jwt\.decode\s*\(/,
8315
- description: "`jwt.decode()` used without signature verification.",
8316
- recommendation: "Use `jwt.verify()` with explicit algorithm pinning to validate token authenticity.",
8317
- fileExtensions: [".js", ".ts"]
8318
- },
8319
- {
8320
- id: "SEC-030",
8321
- category: "SQL Injection",
8322
- severity: "HIGH",
8323
- pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*(?:WHERE|VALUES)\s+.*["']\s*\+\s*[a-zA-Z0-9_.]+/i,
8324
- description: "Unparameterized raw SQL string concatenation detected.",
8325
- recommendation: "Use parameterized queries or ORM bindings (e.g. `$1`, `?`, or named parameters).",
8326
- fileExtensions: [".js", ".ts", ".py", ".go", ".rs"]
8327
- },
8328
- {
8329
- id: "SEC-040",
8330
- category: "SSRF",
8331
- severity: "HIGH",
8332
- pattern: /169\.254\.169\.254/,
8333
- description: "Direct reference to AWS/Cloud instance metadata IP address (169.254.169.254).",
8334
- recommendation: "Restrict outbound HTTP access to metadata endpoints and enforce egress IP filtering."
8656
+ async function promptUserQuestion(params) {
8657
+ const { question, options = [] } = params;
8658
+ const boxWidth = Math.min(process.stdout.columns ?? 80, 75);
8659
+ const border = "\u2500".repeat(Math.max(10, boxWidth - 20));
8660
+ console.log(`
8661
+ ${style.cyan("\u250C\u2500\u2500")} ${style.bold("AI Question")} ${style.cyan(border)}`);
8662
+ const qLines = question.split(`
8663
+ `);
8664
+ for (const line of qLines) {
8665
+ console.log(` ${style.cyan("\u2502")} ${style.bold(line)}`);
8335
8666
  }
8336
- ];
8337
- var IGNORED_DIRS = new Set([
8338
- "node_modules",
8339
- ".git",
8340
- "dist",
8341
- "build",
8342
- "coverage",
8343
- ".next",
8344
- ".turbo",
8345
- "vendor",
8346
- "target",
8347
- "tests",
8348
- "__tests__"
8349
- ]);
8350
- var IGNORED_EXTS = new Set([
8351
- ".md",
8352
- ".mdx",
8353
- ".txt",
8354
- ".log",
8355
- ".svg",
8356
- ".png",
8357
- ".jpg",
8358
- ".jpeg",
8359
- ".ico",
8360
- ".woff",
8361
- ".woff2"
8362
- ]);
8363
- var IGNORED_FILES = new Set([
8364
- "package-lock.json",
8365
- "bun.lock",
8366
- "bun.lockb",
8367
- "yarn.lock",
8368
- "pnpm-lock.yaml",
8369
- "scanner.ts"
8370
- ]);
8371
- async function runSecurityScan(targetDir, options = {}) {
8372
- const startTime = performance.now();
8373
- const root = resolve18(targetDir);
8374
- const isDirectTestDir = targetDir.includes("test") || Boolean(options.includeTests);
8375
- const maxFiles = options.maxFiles || 2000;
8376
- const findings = [];
8377
- let scannedCount = 0;
8378
- function walk(current) {
8379
- if (scannedCount >= maxFiles || !existsSync20(current))
8380
- return;
8381
- let entries;
8382
- try {
8383
- entries = readdirSync8(current);
8384
- } catch {
8385
- return;
8386
- }
8387
- for (const entry of entries) {
8388
- if (scannedCount >= maxFiles)
8389
- break;
8390
- const fullPath = join12(current, entry);
8391
- let stat;
8392
- try {
8393
- stat = statSync5(fullPath);
8394
- } catch {
8395
- continue;
8396
- }
8397
- if (stat.isDirectory()) {
8398
- if (!entry.startsWith(".agent-worktrees") && (!IGNORED_DIRS.has(entry) || isDirectTestDir && (entry === "tests" || entry === "__tests__"))) {
8399
- walk(fullPath);
8400
- }
8401
- } else if (stat.isFile()) {
8402
- if (IGNORED_FILES.has(entry) || stat.size > 2 * 1024 * 1024) {
8403
- continue;
8404
- }
8405
- const ext = entry.includes(".") ? `.${entry.split(".").pop().toLowerCase()}` : "";
8406
- if (IGNORED_EXTS.has(ext)) {
8407
- continue;
8408
- }
8409
- if (options.fileExtensions && options.fileExtensions.length > 0) {
8410
- if (!options.fileExtensions.includes(ext))
8411
- continue;
8412
- }
8413
- scannedCount++;
8414
- scanFile(fullPath, root, ext, findings);
8415
- }
8416
- }
8667
+ if (options.length > 0) {
8668
+ console.log(` ${style.cyan("\u2502")}`);
8669
+ options.forEach((opt, idx) => {
8670
+ const numTag = style.cyan(`[${idx + 1}]`);
8671
+ const isRec = opt.includes("(Recommended)") || opt.includes("(recommended)");
8672
+ const optText = isRec ? opt.replace(/\(Recommended\)/i, style.green("(Recommended)")) : opt;
8673
+ console.log(` ${style.cyan("\u2502")} ${numTag} ${optText}`);
8674
+ });
8417
8675
  }
8418
- function scanFile(filePath, baseRoot, ext, out) {
8419
- let content;
8420
- try {
8421
- content = readFileSync12(filePath, "utf8");
8422
- } catch {
8423
- return;
8424
- }
8425
- const lines = content.split(`
8676
+ console.log(` ${style.cyan("\u2514" + "\u2500".repeat(Math.max(10, boxWidth - 4)))}
8426
8677
  `);
8427
- const relPath = relative2(baseRoot, filePath);
8428
- for (let lineIdx = 0;lineIdx < lines.length; lineIdx++) {
8429
- const line = lines[lineIdx];
8430
- if (line.includes("SECURITY_RULES") || line.includes("pattern: /"))
8431
- continue;
8432
- for (const rule of SECURITY_RULES) {
8433
- if (rule.fileExtensions && ext && !rule.fileExtensions.includes(ext)) {
8434
- continue;
8678
+ if (!process.stdin.isTTY || false || !process.stdin.readable) {
8679
+ return options[0] || "yes";
8680
+ }
8681
+ return new Promise((resolve) => {
8682
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
8683
+ const promptLabel = options.length > 0 ? `Select [1-${options.length}] or type custom response: ` : `Your response: `;
8684
+ rl.question(` ${style.bold(promptLabel)}`, (answer) => {
8685
+ rl.close();
8686
+ const trimmed = answer.trim();
8687
+ if (!trimmed) {
8688
+ const fallback = options[0] || "";
8689
+ console.log(style.dim(` \u21B3 Default: ${fallback || "(empty)"}
8690
+ `));
8691
+ resolve(fallback);
8692
+ return;
8693
+ }
8694
+ const num = parseInt(trimmed, 10);
8695
+ if (!isNaN(num) && num >= 1 && num <= options.length) {
8696
+ const picked = options[num - 1];
8697
+ console.log(style.green(` \u2714 Selected: ${picked}
8698
+ `));
8699
+ resolve(picked);
8700
+ return;
8701
+ }
8702
+ if (options.length === 2) {
8703
+ if (/^(y|yes)$/i.test(trimmed)) {
8704
+ console.log(style.green(` \u2714 Selected: ${options[0]}
8705
+ `));
8706
+ resolve(options[0]);
8707
+ return;
8435
8708
  }
8436
- if (rule.pattern.test(line)) {
8437
- out.push({
8438
- id: rule.id,
8439
- category: rule.category,
8440
- severity: rule.severity,
8441
- filePath: relPath,
8442
- lineNumber: lineIdx + 1,
8443
- snippet: line.trim().slice(0, 120),
8444
- description: rule.description,
8445
- recommendation: rule.recommendation
8446
- });
8709
+ if (/^(n|no)$/i.test(trimmed)) {
8710
+ console.log(style.green(` \u2714 Selected: ${options[1]}
8711
+ `));
8712
+ resolve(options[1]);
8713
+ return;
8447
8714
  }
8448
8715
  }
8449
- }
8450
- }
8451
- walk(root);
8452
- const durationMs = performance.now() - startTime;
8453
- const summary = {
8454
- critical: findings.filter((f) => f.severity === "CRITICAL").length,
8455
- high: findings.filter((f) => f.severity === "HIGH").length,
8456
- medium: findings.filter((f) => f.severity === "MEDIUM").length,
8457
- low: findings.filter((f) => f.severity === "LOW").length
8458
- };
8459
- return {
8460
- scannedFiles: scannedCount,
8461
- findings,
8462
- durationMs,
8463
- summary
8464
- };
8716
+ console.log(style.green(` \u2714 Answer: ${trimmed}
8717
+ `));
8718
+ resolve(trimmed);
8719
+ });
8720
+ });
8465
8721
  }
8466
-
8467
- // src/mcp/servers/chrome-devtools/index.ts
8468
- import { resolve as resolve19 } from "path";
8469
-
8470
- // src/mcp/servers/chrome-devtools/server.ts
8471
- if (false) {}
8472
-
8473
- // src/mcp/servers/chrome-devtools/index.ts
8474
- var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/chrome-devtools";
8475
- var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve19(__dirname, "server.ts");
8476
-
8477
- // src/mcp/servers/web-search/index.ts
8478
- import { resolve as resolve20 } from "path";
8479
-
8480
- // src/mcp/servers/web-search/server.ts
8481
- if (false) {}
8482
-
8483
- // src/mcp/servers/web-search/index.ts
8484
- var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/web-search";
8485
- var WEB_SEARCH_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
8486
-
8487
- // src/mcp/servers/sqlite/index.ts
8488
- import { resolve as resolve21 } from "path";
8489
-
8490
- // src/mcp/servers/sqlite/server.ts
8491
- if (false) {}
8492
-
8493
- // src/mcp/servers/sqlite/index.ts
8494
- var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
8495
- var SQLITE_MCP_SERVER_PATH = resolve21(__dirname, "server.ts");
8496
-
8497
- // src/init/project-analyzer.ts
8498
- import { existsSync as existsSync21, readFileSync as readFileSync13, readdirSync as readdirSync9 } from "fs";
8499
- import { join as join13, basename as basename3 } from "path";
8500
-
8501
- class ProjectAnalyzer {
8502
- cwd;
8503
- constructor(cwd = process.cwd()) {
8504
- this.cwd = cwd;
8722
+ async function promptInteractiveList(config) {
8723
+ const {
8724
+ title,
8725
+ items,
8726
+ mode = "select",
8727
+ defaultIndex = 0,
8728
+ maxVisible = 8,
8729
+ emptyMessage = "No items available.",
8730
+ onToggle,
8731
+ onAction,
8732
+ customKeyHints
8733
+ } = config;
8734
+ if (!process.stdin.isTTY || false || !process.stdin.readable) {
8735
+ return {
8736
+ selectedIndex: defaultIndex,
8737
+ selectedItem: items[defaultIndex] || items[0],
8738
+ action: "select"
8739
+ };
8505
8740
  }
8506
- analyze() {
8507
- const readmeInfo = this.extractReadmeMetadata();
8508
- const projectName = readmeInfo.title || this.detectProjectName();
8509
- const languages = this.detectLanguages();
8510
- const packageManager = this.detectPackageManager();
8511
- const frameworks = [];
8512
- const infrastructure = [];
8513
- const commands = {};
8514
- const architectureNotes = [];
8515
- const codeConventions = [];
8516
- let description = readmeInfo.description;
8517
- const pkgPath = join13(this.cwd, "package.json");
8518
- if (existsSync21(pkgPath)) {
8519
- try {
8520
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
8521
- if (!description && pkg.description)
8522
- description = pkg.description;
8523
- const pm = packageManager || "npm";
8524
- const runPrefix = pm === "bun" || pm === "yarn" || pm === "pnpm" ? `${pm} run` : "npm run";
8525
- const testPrefix = pm === "bun" ? "bun test" : pm === "pnpm" ? "pnpm test" : pm === "yarn" ? "yarn test" : "npm test";
8526
- if (pkg.scripts) {
8527
- if (pkg.scripts.dev)
8528
- commands.dev = `${runPrefix} dev`;
8529
- else if (pkg.scripts.start)
8530
- commands.dev = `${runPrefix} start`;
8531
- if (pkg.scripts.build)
8532
- commands.build = `${runPrefix} build`;
8533
- if (pkg.scripts.test)
8534
- commands.test = pkg.scripts.test === "bun test" ? "bun test" : testPrefix;
8535
- if (pkg.scripts.typecheck)
8536
- commands.typecheck = `${runPrefix} typecheck`;
8537
- else if (pkg.scripts.check)
8538
- commands.typecheck = `${runPrefix} check`;
8539
- if (pkg.scripts.lint)
8540
- commands.lint = `${runPrefix} lint`;
8541
- if (pkg.scripts.format)
8542
- commands.format = `${runPrefix} format`;
8741
+ if (items.length === 0) {
8742
+ console.log(`
8743
+ ${style.dim(emptyMessage)}
8744
+ `);
8745
+ return { selectedIndex: -1, action: "close" };
8746
+ }
8747
+ return new Promise((resolve) => {
8748
+ let selectedIndex = Math.max(0, Math.min(defaultIndex, items.length - 1));
8749
+ let scrollTop = 0;
8750
+ let renderedLines = 0;
8751
+ const wasRaw = process.stdin.isRaw ?? false;
8752
+ ensureRawMode(true);
8753
+ process.stdout.write("\x1B[?25l");
8754
+ const BOX_WIDTH = Math.min(process.stdout.columns ?? 80, 76);
8755
+ const ensureVisible = () => {
8756
+ const visibleCount = Math.min(items.length, maxVisible);
8757
+ if (selectedIndex < scrollTop) {
8758
+ scrollTop = selectedIndex;
8759
+ } else if (selectedIndex >= scrollTop + visibleCount) {
8760
+ scrollTop = selectedIndex + 1 - visibleCount;
8761
+ }
8762
+ if (scrollTop < 0)
8763
+ scrollTop = 0;
8764
+ const maxScroll = Math.max(0, items.length - visibleCount);
8765
+ if (scrollTop > maxScroll)
8766
+ scrollTop = maxScroll;
8767
+ };
8768
+ const render = () => {
8769
+ ensureVisible();
8770
+ const visibleCount = Math.min(items.length, maxVisible);
8771
+ const visibleItems = items.slice(scrollTop, scrollTop + visibleCount);
8772
+ const lines = [];
8773
+ const headerBorder = "\u2500".repeat(Math.max(10, BOX_WIDTH - title.length - 8));
8774
+ lines.push(` \x1B[38;2;140;140;150m\u250C\u2500\u2500\x1B[0m ${style.bold(title)} \x1B[38;2;140;140;150m${headerBorder}\u2510\x1B[0m`);
8775
+ for (let i = 0;i < visibleItems.length; i++) {
8776
+ const item = visibleItems[i];
8777
+ const actualIdx = scrollTop + i;
8778
+ const isCurrent = actualIdx === selectedIndex;
8779
+ const marker = isCurrent ? "\x1B[38;2;217;119;87m\u276F\x1B[0m" : " ";
8780
+ let checkSymbol = "";
8781
+ if (mode === "toggle") {
8782
+ checkSymbol = item.checked ? "\x1B[38;2;78;169;111m[\u2714 ENABLED]\x1B[0m " : "\x1B[38;2;120;120;125m[\u25CB DISABLED]\x1B[0m";
8783
+ } else if (item.checked !== undefined) {
8784
+ checkSymbol = item.checked ? "\x1B[38;2;78;169;111m(\u25CF)\x1B[0m " : "\x1B[38;2;120;120;125m(\u25CB)\x1B[0m ";
8543
8785
  }
8544
- const allDeps = {
8545
- ...pkg.dependencies || {},
8546
- ...pkg.devDependencies || {}
8547
- };
8548
- if (allDeps.next)
8549
- frameworks.push("Next.js");
8550
- if (allDeps.react)
8551
- frameworks.push("React");
8552
- if (allDeps.vue)
8553
- frameworks.push("Vue.js");
8554
- if (allDeps.svelte || allDeps["@sveltejs/kit"])
8555
- frameworks.push("Svelte");
8556
- if (allDeps.astro)
8557
- frameworks.push("Astro");
8558
- if (allDeps.vite)
8559
- frameworks.push("Vite");
8560
- if (allDeps.express)
8561
- frameworks.push("Express");
8562
- if (allDeps.hono)
8563
- frameworks.push("Hono");
8564
- if (allDeps.fastify)
8565
- frameworks.push("Fastify");
8566
- if (allDeps["@nestjs/core"])
8567
- frameworks.push("NestJS");
8568
- if (allDeps.tailwindcss)
8569
- frameworks.push("TailwindCSS");
8570
- if (allDeps["lucide-react"] || allDeps.lucide)
8571
- frameworks.push("Lucide Icons");
8572
- if (allDeps.zustand)
8573
- frameworks.push("Zustand");
8574
- if (allDeps["@tanstack/react-query"])
8575
- frameworks.push("TanStack Query");
8576
- if (allDeps.oxlint)
8577
- frameworks.push("Oxlint");
8578
- if (allDeps.eslint)
8579
- frameworks.push("ESLint");
8580
- if (allDeps.vitest)
8581
- frameworks.push("Vitest");
8582
- if (allDeps.jest)
8583
- frameworks.push("Jest");
8584
- if (allDeps.playwright || allDeps["@playwright/test"])
8585
- frameworks.push("Playwright");
8586
- if (pkg.type === "module") {
8587
- codeConventions.push("Use ES modules (`import/export`), not CommonJS (`require`).");
8786
+ const badgeStr = item.badge ? ` ${style.dim(`(${item.badge})`)}` : "";
8787
+ const labelStr = isCurrent ? style.bold(item.label) : item.label;
8788
+ lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m ${marker} ${checkSymbol} ${labelStr}${badgeStr}`);
8789
+ if (item.description) {
8790
+ const descIndent = " ".repeat(mode === "toggle" ? 17 : 7);
8791
+ const maxDescLen = BOX_WIDTH - descIndent.length - 6;
8792
+ const trimmedDesc = item.description.length > maxDescLen ? item.description.slice(0, maxDescLen - 3) + "..." : item.description;
8793
+ lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m ${descIndent}${style.dim(trimmedDesc)}`);
8588
8794
  }
8589
- } catch {}
8590
- }
8591
- const tsconfigPath = join13(this.cwd, "tsconfig.json");
8592
- if (existsSync21(tsconfigPath)) {
8593
- try {
8594
- const tsconfig = JSON.parse(readFileSync13(tsconfigPath, "utf8"));
8595
- if (tsconfig.compilerOptions?.strict) {
8596
- codeConventions.push("TypeScript strict mode enabled.");
8795
+ }
8796
+ const moreAbove = scrollTop;
8797
+ const moreBelow = Math.max(0, items.length - (scrollTop + visibleCount));
8798
+ let scrollInfo = "";
8799
+ if (moreAbove > 0 && moreBelow > 0) {
8800
+ scrollInfo = `[\u2191 ${moreAbove} more \xB7 \u2193 ${moreBelow} more]`;
8801
+ } else if (moreBelow > 0) {
8802
+ scrollInfo = `[\u2193 ${moreBelow} more below]`;
8803
+ } else if (moreAbove > 0) {
8804
+ scrollInfo = `[\u2191 ${moreAbove} more above]`;
8805
+ }
8806
+ const defaultHints = mode === "toggle" ? "\u2191/\u2193: navigate \xB7 Space: toggle \xB7 a: all \xB7 d: none \xB7 Enter/Esc: done" : "\u2191/\u2193: navigate \xB7 Enter: select \xB7 Esc: cancel";
8807
+ const hintText = customKeyHints || (scrollInfo ? `${scrollInfo} ${defaultHints}` : defaultHints);
8808
+ lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m`);
8809
+ lines.push(` \x1B[38;2;140;140;150m\u2502\x1B[0m ${style.dim(hintText)}`);
8810
+ lines.push(` \x1B[38;2;140;140;150m\u2514\u2500\u2500${"\u2500".repeat(Math.max(10, BOX_WIDTH - 6))}\u2518\x1B[0m`);
8811
+ let buffer = "";
8812
+ if (renderedLines > 0) {
8813
+ buffer += `\x1B[${renderedLines}A\r`;
8814
+ }
8815
+ buffer += lines.map((l) => `\x1B[2K${l}`).join(`
8816
+ `) + `
8817
+ `;
8818
+ if (renderedLines > lines.length) {
8819
+ buffer += "\x1B[J";
8820
+ }
8821
+ process.stdout.write(buffer);
8822
+ renderedLines = lines.length;
8823
+ };
8824
+ let unsubscribe = null;
8825
+ const cleanup = (res) => {
8826
+ if (renderedLines > 0) {
8827
+ process.stdout.write(`\x1B[${renderedLines}A\r\x1B[J\x1B[?25h`);
8828
+ renderedLines = 0;
8829
+ } else {
8830
+ process.stdout.write("\x1B[?25h");
8831
+ }
8832
+ if (unsubscribe) {
8833
+ unsubscribe();
8834
+ unsubscribe = null;
8835
+ }
8836
+ ensureRawMode(wasRaw);
8837
+ resolve(res);
8838
+ };
8839
+ const onKeypress = async (_str, key) => {
8840
+ if (!key)
8841
+ return;
8842
+ if (key.ctrl && key.name === "c" || key.name === "escape" || _str === "q" || _str === "Q") {
8843
+ cleanup({
8844
+ selectedIndex,
8845
+ selectedItem: items[selectedIndex],
8846
+ action: "close"
8847
+ });
8848
+ return;
8849
+ }
8850
+ if (key.name === "up" || _str === "k") {
8851
+ selectedIndex = (selectedIndex - 1 + items.length) % items.length;
8852
+ render();
8853
+ return;
8854
+ }
8855
+ if (key.name === "down" || _str === "j") {
8856
+ selectedIndex = (selectedIndex + 1) % items.length;
8857
+ render();
8858
+ return;
8859
+ }
8860
+ if (_str === " " || _str === "t" || _str === "T") {
8861
+ const item = items[selectedIndex];
8862
+ if (item) {
8863
+ item.checked = !item.checked;
8864
+ if (onToggle) {
8865
+ await onToggle(item, selectedIndex);
8866
+ }
8597
8867
  }
8598
- if (!commands.typecheck) {
8599
- commands.typecheck = "tsc --noEmit";
8868
+ render();
8869
+ return;
8870
+ }
8871
+ if (key.name === "return" || key.name === "enter") {
8872
+ if (mode === "toggle") {
8873
+ cleanup({
8874
+ selectedIndex,
8875
+ selectedItem: items[selectedIndex],
8876
+ action: "close"
8877
+ });
8878
+ } else {
8879
+ cleanup({
8880
+ selectedIndex,
8881
+ selectedItem: items[selectedIndex],
8882
+ action: "select"
8883
+ });
8600
8884
  }
8601
- } catch {}
8602
- }
8603
- const cargoPath = join13(this.cwd, "Cargo.toml");
8604
- if (existsSync21(cargoPath)) {
8605
- try {
8606
- commands.dev = commands.dev || "cargo run";
8607
- commands.build = commands.build || "cargo build";
8608
- commands.test = commands.test || "cargo test";
8609
- commands.lint = commands.lint || "cargo clippy";
8610
- frameworks.push("Rust Cargo");
8611
- } catch {}
8612
- }
8613
- const goModPath = join13(this.cwd, "go.mod");
8614
- if (existsSync21(goModPath)) {
8615
- try {
8616
- commands.dev = commands.dev || "go run .";
8617
- commands.build = commands.build || "go build ./...";
8618
- commands.test = commands.test || "go test ./...";
8619
- commands.lint = commands.lint || "golangci-lint run";
8620
- frameworks.push("Go Modules");
8621
- } catch {}
8622
- }
8623
- const pyprojectPath = join13(this.cwd, "pyproject.toml");
8624
- const requirementsPath = join13(this.cwd, "requirements.txt");
8625
- if (existsSync21(pyprojectPath) || existsSync21(requirementsPath)) {
8626
- commands.test = commands.test || "pytest";
8627
- commands.lint = commands.lint || "ruff check .";
8628
- if (existsSync21(join13(this.cwd, "uv.lock"))) {
8629
- frameworks.push("uv");
8630
- commands.test = "uv run pytest";
8631
- } else if (existsSync21(join13(this.cwd, "poetry.lock"))) {
8632
- frameworks.push("Poetry");
8633
- commands.test = "poetry run pytest";
8885
+ return;
8634
8886
  }
8635
- }
8636
- if (existsSync21(join13(this.cwd, "Dockerfile"))) {
8637
- infrastructure.push("Docker");
8638
- const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
8639
- commands.dockerBuild = `docker build -t ${sanitizedName || "app"} .`;
8640
- }
8641
- if (existsSync21(join13(this.cwd, "nginx.conf"))) {
8642
- infrastructure.push("Nginx");
8643
- }
8644
- if (existsSync21(join13(this.cwd, "src/api.ts")) || existsSync21(join13(this.cwd, "src/api"))) {
8645
- architectureNotes.push("Backend API endpoints and network client logic are centralized in `src/api`.");
8646
- }
8647
- if (existsSync21(join13(this.cwd, "src/components"))) {
8648
- architectureNotes.push("Reusable UI presentation components live in `src/components/`.");
8649
- }
8650
- if (existsSync21(join13(this.cwd, "src/types.ts")) || existsSync21(join13(this.cwd, "src/types"))) {
8651
- architectureNotes.push("Shared TypeScript data models and interfaces are defined in `src/types`.");
8652
- }
8653
- if (existsSync21(join13(this.cwd, ".env.example"))) {
8654
- architectureNotes.push("Environment configuration template is in `.env.example`.");
8655
- }
8656
- if (commands.typecheck || commands.lint || commands.test) {
8657
- const checks = [];
8658
- if (commands.typecheck)
8659
- checks.push(`typecheck (\`${commands.typecheck}\`)`);
8660
- if (commands.lint)
8661
- checks.push(`lint (\`${commands.lint}\`)`);
8662
- if (commands.test)
8663
- checks.push(`tests (\`${commands.test}\`)`);
8664
- codeConventions.push(`Run ${checks.join(" and ")} before concluding any major code edits.`);
8665
- }
8666
- const instructionFiles = ["AGENTS.md", "CLAUDE.md", ".agents.md", "AGENTS.override.md"];
8667
- let hasExistingInstructions = false;
8668
- let existingInstructionFile;
8669
- for (const f of instructionFiles) {
8670
- if (existsSync21(join13(this.cwd, f))) {
8671
- hasExistingInstructions = true;
8672
- existingInstructionFile = f;
8673
- break;
8887
+ if (_str && onAction) {
8888
+ const item = items[selectedIndex];
8889
+ const shouldExit = await onAction(_str.toLowerCase(), item, selectedIndex);
8890
+ if (shouldExit) {
8891
+ cleanup({
8892
+ selectedIndex,
8893
+ selectedItem: item,
8894
+ action: "custom",
8895
+ keyName: _str.toLowerCase()
8896
+ });
8897
+ return;
8898
+ }
8899
+ render();
8674
8900
  }
8675
- }
8676
- return {
8677
- projectName,
8678
- description,
8679
- languages,
8680
- packageManager,
8681
- frameworks,
8682
- infrastructure,
8683
- commands,
8684
- architectureNotes,
8685
- codeConventions,
8686
- hasExistingInstructions,
8687
- existingInstructionFile
8688
8901
  };
8902
+ unsubscribe = addGlobalKeypressListener(onKeypress);
8903
+ render();
8904
+ });
8905
+ }
8906
+
8907
+ // src/security/scanner.ts
8908
+ import { existsSync as existsSync22, readdirSync as readdirSync9, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
8909
+ import { join as join14, relative as relative2, resolve as resolve18 } from "path";
8910
+ var SECURITY_RULES = [
8911
+ {
8912
+ id: "SEC-001",
8913
+ category: "Secrets Leakage",
8914
+ severity: "CRITICAL",
8915
+ pattern: /sk-(?:proj-)?[A-Za-z0-9-_]{20,}/,
8916
+ description: "Hardcoded OpenAI API key detected in source code.",
8917
+ recommendation: "Store API keys in environment variables (e.g. process.env.OPENAI_API_KEY) or a secure vault."
8918
+ },
8919
+ {
8920
+ id: "SEC-002",
8921
+ category: "Secrets Leakage",
8922
+ severity: "CRITICAL",
8923
+ pattern: /AKIA[0-9A-Z]{16}/,
8924
+ description: "Exposed AWS Access Key ID found in source code.",
8925
+ recommendation: "Rotate the exposed key immediately and use IAM Roles or AWS environment credentials."
8926
+ },
8927
+ {
8928
+ id: "SEC-003",
8929
+ category: "Secrets Leakage",
8930
+ severity: "CRITICAL",
8931
+ pattern: /ghp_[0-9a-zA-Z]{36}/,
8932
+ description: "Exposed GitHub Personal Access Token (PAT) detected.",
8933
+ recommendation: "Revoke the token and inject credentials via GitHub Actions Secrets or environment variables."
8934
+ },
8935
+ {
8936
+ id: "SEC-004",
8937
+ category: "Secrets Leakage",
8938
+ severity: "CRITICAL",
8939
+ pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/,
8940
+ description: "Unencrypted private cryptographic key committed to source repository.",
8941
+ recommendation: "Remove private key from git history and manage certificates through a secret manager."
8942
+ },
8943
+ {
8944
+ id: "SEC-005",
8945
+ category: "Secrets Leakage",
8946
+ severity: "HIGH",
8947
+ pattern: /(?:postgres|mysql|mongodb(?:\+srv)?):\/\/[a-zA-Z0-9_.-]+:[^@\s"']+@[a-zA-Z0-9_.-]+/i,
8948
+ description: "Hardcoded database connection string containing plaintext credentials.",
8949
+ recommendation: "Extract connection URI into DATABASE_URL environment variable."
8950
+ },
8951
+ {
8952
+ id: "SEC-010",
8953
+ category: "Code Injection",
8954
+ severity: "HIGH",
8955
+ pattern: /\beval\s*\(/,
8956
+ description: "Use of dangerous `eval()` function permits arbitrary code execution.",
8957
+ recommendation: "Avoid eval(). Use structured JSON.parse() or dedicated domain-specific parsers.",
8958
+ fileExtensions: [".js", ".ts", ".jsx", ".tsx", ".py"]
8959
+ },
8960
+ {
8961
+ id: "SEC-011",
8962
+ category: "Command Injection",
8963
+ severity: "HIGH",
8964
+ pattern: /(?:child_process|cp)\.exec\s*\([^,)]*\+/,
8965
+ description: "Dynamic string concatenation in `child_process.exec()` creates command injection vectors.",
8966
+ recommendation: "Use `execFile` or `spawn` with an array of arguments rather than executing raw shell strings.",
8967
+ fileExtensions: [".js", ".ts"]
8968
+ },
8969
+ {
8970
+ id: "SEC-020",
8971
+ category: "Broken Authentication",
8972
+ severity: "MEDIUM",
8973
+ pattern: /jwt\.(?:sign|verify)\s*\([^,]+,\s*["'](?:secret|test|123456|dev|password)["']\s*\)/i,
8974
+ description: "Weak or hardcoded JWT secret key used in token signing/verification.",
8975
+ recommendation: "Use a high-entropy secret (at least 256 bits) loaded securely from environment variables.",
8976
+ fileExtensions: [".js", ".ts", ".py"]
8977
+ },
8978
+ {
8979
+ id: "SEC-021",
8980
+ category: "Broken Authentication",
8981
+ severity: "MEDIUM",
8982
+ pattern: /jwt\.decode\s*\(/,
8983
+ description: "`jwt.decode()` used without signature verification.",
8984
+ recommendation: "Use `jwt.verify()` with explicit algorithm pinning to validate token authenticity.",
8985
+ fileExtensions: [".js", ".ts"]
8986
+ },
8987
+ {
8988
+ id: "SEC-030",
8989
+ category: "SQL Injection",
8990
+ severity: "HIGH",
8991
+ pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*(?:WHERE|VALUES)\s+.*["']\s*\+\s*[a-zA-Z0-9_.]+/i,
8992
+ description: "Unparameterized raw SQL string concatenation detected.",
8993
+ recommendation: "Use parameterized queries or ORM bindings (e.g. `$1`, `?`, or named parameters).",
8994
+ fileExtensions: [".js", ".ts", ".py", ".go", ".rs"]
8995
+ },
8996
+ {
8997
+ id: "SEC-040",
8998
+ category: "SSRF",
8999
+ severity: "HIGH",
9000
+ pattern: /169\.254\.169\.254/,
9001
+ description: "Direct reference to AWS/Cloud instance metadata IP address (169.254.169.254).",
9002
+ recommendation: "Restrict outbound HTTP access to metadata endpoints and enforce egress IP filtering."
8689
9003
  }
8690
- generateAgentsMarkdown(analysis) {
8691
- const lines = [];
8692
- lines.push(`# ${analysis.projectName}`);
8693
- lines.push("");
8694
- if (analysis.description) {
8695
- lines.push(`> ${analysis.description}`);
8696
- lines.push("");
8697
- }
8698
- lines.push("## Commands");
8699
- lines.push("");
8700
- if (Object.keys(analysis.commands).length > 0) {
8701
- if (analysis.commands.dev)
8702
- lines.push(`- **Dev Server**: \`${analysis.commands.dev}\``);
8703
- if (analysis.commands.build)
8704
- lines.push(`- **Build**: \`${analysis.commands.build}\``);
8705
- if (analysis.commands.test)
8706
- lines.push(`- **Test**: \`${analysis.commands.test}\``);
8707
- if (analysis.commands.typecheck)
8708
- lines.push(`- **Typecheck**: \`${analysis.commands.typecheck}\``);
8709
- if (analysis.commands.lint)
8710
- lines.push(`- **Lint**: \`${analysis.commands.lint}\``);
8711
- if (analysis.commands.format)
8712
- lines.push(`- **Format**: \`${analysis.commands.format}\``);
8713
- if (analysis.commands.dockerBuild)
8714
- lines.push(`- **Docker Build**: \`${analysis.commands.dockerBuild}\``);
8715
- } else {
8716
- lines.push("- *No standard build/test commands detected.*");
8717
- }
8718
- lines.push("");
8719
- lines.push("## Architecture & Stack");
8720
- lines.push("");
8721
- const stackItems = [];
8722
- if (analysis.languages.length > 0)
8723
- stackItems.push(analysis.languages.join(", "));
8724
- if (analysis.frameworks.length > 0)
8725
- stackItems.push(analysis.frameworks.join(", "));
8726
- if (analysis.infrastructure.length > 0)
8727
- stackItems.push(analysis.infrastructure.join(", "));
8728
- if (stackItems.length > 0) {
8729
- lines.push(`- **Core Stack**: ${stackItems.join(" \u2022 ")}`);
8730
- }
8731
- for (const note of analysis.architectureNotes) {
8732
- lines.push(`- ${note}`);
8733
- }
8734
- lines.push("");
8735
- lines.push("## Workflow & Code Guidelines");
8736
- lines.push("");
8737
- if (analysis.codeConventions.length > 0) {
8738
- for (const conv of analysis.codeConventions) {
8739
- lines.push(`- ${conv}`);
8740
- }
8741
- }
8742
- lines.push("- Prefer targeted edits over whole-file rewrites.");
8743
- lines.push("- When fixing errors, address the root cause rather than suppressing compiler warnings.");
8744
- lines.push("");
8745
- return lines.join(`
8746
- `);
8747
- }
8748
- extractReadmeMetadata() {
8749
- const readmeFiles = ["README.md", "readme.md", "README.MD"];
8750
- for (const file of readmeFiles) {
8751
- const fullPath = join13(this.cwd, file);
8752
- if (existsSync21(fullPath)) {
8753
- try {
8754
- const content = readFileSync13(fullPath, "utf8");
8755
- const lines = content.split(`
8756
- `);
8757
- let title;
8758
- let description;
8759
- for (const line of lines) {
8760
- const trimmed = line.trim();
8761
- if (!title && trimmed.startsWith("# ")) {
8762
- title = trimmed.replace(/^#\s+/, "").trim();
8763
- continue;
8764
- }
8765
- if (title && !description && trimmed.length > 0 && !trimmed.startsWith("#") && !trimmed.startsWith("```") && !trimmed.startsWith("[")) {
8766
- description = trimmed;
8767
- break;
8768
- }
8769
- }
8770
- return { title, description };
8771
- } catch {}
8772
- }
9004
+ ];
9005
+ var IGNORED_DIRS = new Set([
9006
+ "node_modules",
9007
+ ".git",
9008
+ "dist",
9009
+ "build",
9010
+ "coverage",
9011
+ ".next",
9012
+ ".turbo",
9013
+ "vendor",
9014
+ "target",
9015
+ "tests",
9016
+ "__tests__"
9017
+ ]);
9018
+ var IGNORED_EXTS = new Set([
9019
+ ".md",
9020
+ ".mdx",
9021
+ ".txt",
9022
+ ".log",
9023
+ ".svg",
9024
+ ".png",
9025
+ ".jpg",
9026
+ ".jpeg",
9027
+ ".ico",
9028
+ ".woff",
9029
+ ".woff2"
9030
+ ]);
9031
+ var IGNORED_FILES = new Set([
9032
+ "package-lock.json",
9033
+ "bun.lock",
9034
+ "bun.lockb",
9035
+ "yarn.lock",
9036
+ "pnpm-lock.yaml",
9037
+ "scanner.ts"
9038
+ ]);
9039
+ async function runSecurityScan(targetDir, options = {}) {
9040
+ const startTime = performance.now();
9041
+ const root = resolve18(targetDir);
9042
+ const isDirectTestDir = targetDir.includes("test") || Boolean(options.includeTests);
9043
+ const maxFiles = options.maxFiles || 2000;
9044
+ const findings = [];
9045
+ let scannedCount = 0;
9046
+ function walk(current) {
9047
+ if (scannedCount >= maxFiles || !existsSync22(current))
9048
+ return;
9049
+ let entries;
9050
+ try {
9051
+ entries = readdirSync9(current);
9052
+ } catch {
9053
+ return;
8773
9054
  }
8774
- return {};
8775
- }
8776
- detectProjectName() {
8777
- const pkgPath = join13(this.cwd, "package.json");
8778
- if (existsSync21(pkgPath)) {
9055
+ for (const entry of entries) {
9056
+ if (scannedCount >= maxFiles)
9057
+ break;
9058
+ const fullPath = join14(current, entry);
9059
+ let stat;
8779
9060
  try {
8780
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
8781
- if (pkg.name && pkg.name !== "frontend" && pkg.name !== "backend" && pkg.name !== "app") {
8782
- return pkg.name.startsWith("@") ? pkg.name.split("/")[1] || pkg.name : pkg.name;
9061
+ stat = statSync6(fullPath);
9062
+ } catch {
9063
+ continue;
9064
+ }
9065
+ if (stat.isDirectory()) {
9066
+ if (!entry.startsWith(".agent-worktrees") && (!IGNORED_DIRS.has(entry) || isDirectTestDir && (entry === "tests" || entry === "__tests__"))) {
9067
+ walk(fullPath);
8783
9068
  }
8784
- } catch {}
8785
- }
8786
- const cargoPath = join13(this.cwd, "Cargo.toml");
8787
- if (existsSync21(cargoPath)) {
8788
- try {
8789
- const match = readFileSync13(cargoPath, "utf8").match(/name\s*=\s*"([^"]+)"/);
8790
- if (match?.[1])
8791
- return match[1];
8792
- } catch {}
8793
- }
8794
- const goModPath = join13(this.cwd, "go.mod");
8795
- if (existsSync21(goModPath)) {
8796
- try {
8797
- const match = readFileSync13(goModPath, "utf8").match(/module\s+([^\s]+)/);
8798
- if (match?.[1])
8799
- return basename3(match[1]);
8800
- } catch {}
8801
- }
8802
- return basename3(this.cwd);
8803
- }
8804
- detectLanguages() {
8805
- const langs = new Set;
8806
- if (existsSync21(join13(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
8807
- langs.add("TypeScript");
8808
- }
8809
- if (existsSync21(join13(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
8810
- langs.add("JavaScript");
8811
- }
8812
- if (existsSync21(join13(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
8813
- langs.add("Rust");
8814
- }
8815
- if (existsSync21(join13(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
8816
- langs.add("Go");
8817
- }
8818
- if (existsSync21(join13(this.cwd, "pyproject.toml")) || existsSync21(join13(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
8819
- langs.add("Python");
8820
- }
8821
- if (existsSync21(join13(this.cwd, "pom.xml")) || existsSync21(join13(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
8822
- langs.add("Java");
8823
- }
8824
- if (existsSync21(join13(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
8825
- langs.add("C/C++");
9069
+ } else if (stat.isFile()) {
9070
+ if (IGNORED_FILES.has(entry) || stat.size > 2 * 1024 * 1024) {
9071
+ continue;
9072
+ }
9073
+ const ext = entry.includes(".") ? `.${entry.split(".").pop().toLowerCase()}` : "";
9074
+ if (IGNORED_EXTS.has(ext)) {
9075
+ continue;
9076
+ }
9077
+ if (options.fileExtensions && options.fileExtensions.length > 0) {
9078
+ if (!options.fileExtensions.includes(ext))
9079
+ continue;
9080
+ }
9081
+ scannedCount++;
9082
+ scanFile(fullPath, root, ext, findings);
9083
+ }
8826
9084
  }
8827
- return Array.from(langs);
8828
- }
8829
- detectPackageManager() {
8830
- if (existsSync21(join13(this.cwd, "bun.lockb")) || existsSync21(join13(this.cwd, "bun.lock")))
8831
- return "bun";
8832
- if (existsSync21(join13(this.cwd, "pnpm-lock.yaml")))
8833
- return "pnpm";
8834
- if (existsSync21(join13(this.cwd, "yarn.lock")))
8835
- return "yarn";
8836
- if (existsSync21(join13(this.cwd, "package-lock.json")))
8837
- return "npm";
8838
- if (existsSync21(join13(this.cwd, "Cargo.lock")) || existsSync21(join13(this.cwd, "Cargo.toml")))
8839
- return "cargo";
8840
- if (existsSync21(join13(this.cwd, "uv.lock")))
8841
- return "uv";
8842
- if (existsSync21(join13(this.cwd, "poetry.lock")))
8843
- return "poetry";
8844
- if (existsSync21(join13(this.cwd, "go.sum")) || existsSync21(join13(this.cwd, "go.mod")))
8845
- return "go";
8846
- if (existsSync21(join13(this.cwd, "package.json")))
8847
- return "npm";
8848
- return;
8849
9085
  }
8850
- hasFileWithExtension(...exts) {
9086
+ function scanFile(filePath, baseRoot, ext, out) {
9087
+ let content;
8851
9088
  try {
8852
- const entries = readdirSync9(this.cwd);
8853
- return entries.some((e) => exts.some((ext) => e.endsWith(ext)));
9089
+ content = readFileSync14(filePath, "utf8");
8854
9090
  } catch {
8855
- return false;
9091
+ return;
9092
+ }
9093
+ const lines = content.split(`
9094
+ `);
9095
+ const relPath = relative2(baseRoot, filePath);
9096
+ for (let lineIdx = 0;lineIdx < lines.length; lineIdx++) {
9097
+ const line = lines[lineIdx];
9098
+ if (line.includes("SECURITY_RULES") || line.includes("pattern: /"))
9099
+ continue;
9100
+ for (const rule of SECURITY_RULES) {
9101
+ if (rule.fileExtensions && ext && !rule.fileExtensions.includes(ext)) {
9102
+ continue;
9103
+ }
9104
+ if (rule.pattern.test(line)) {
9105
+ out.push({
9106
+ id: rule.id,
9107
+ category: rule.category,
9108
+ severity: rule.severity,
9109
+ filePath: relPath,
9110
+ lineNumber: lineIdx + 1,
9111
+ snippet: line.trim().slice(0, 120),
9112
+ description: rule.description,
9113
+ recommendation: rule.recommendation
9114
+ });
9115
+ }
9116
+ }
8856
9117
  }
8857
9118
  }
9119
+ walk(root);
9120
+ const durationMs = performance.now() - startTime;
9121
+ const summary = {
9122
+ critical: findings.filter((f) => f.severity === "CRITICAL").length,
9123
+ high: findings.filter((f) => f.severity === "HIGH").length,
9124
+ medium: findings.filter((f) => f.severity === "MEDIUM").length,
9125
+ low: findings.filter((f) => f.severity === "LOW").length
9126
+ };
9127
+ return {
9128
+ scannedFiles: scannedCount,
9129
+ findings,
9130
+ durationMs,
9131
+ summary
9132
+ };
8858
9133
  }
9134
+
9135
+ // src/mcp/servers/chrome-devtools/index.ts
9136
+ import { resolve as resolve19 } from "path";
9137
+
9138
+ // src/mcp/servers/chrome-devtools/server.ts
9139
+ if (false) {}
9140
+
9141
+ // src/mcp/servers/chrome-devtools/index.ts
9142
+ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/chrome-devtools";
9143
+ var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve19(__dirname, "server.ts");
9144
+
9145
+ // src/mcp/servers/web-search/index.ts
9146
+ import { resolve as resolve20 } from "path";
9147
+
9148
+ // src/mcp/servers/web-search/server.ts
9149
+ if (false) {}
9150
+
9151
+ // src/mcp/servers/web-search/index.ts
9152
+ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/web-search";
9153
+ var WEB_SEARCH_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
9154
+
9155
+ // src/mcp/servers/sqlite/index.ts
9156
+ import { resolve as resolve21 } from "path";
9157
+
9158
+ // src/mcp/servers/sqlite/server.ts
9159
+ if (false) {}
9160
+
9161
+ // src/mcp/servers/sqlite/index.ts
9162
+ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
9163
+ var SQLITE_MCP_SERVER_PATH = resolve21(__dirname, "server.ts");
8859
9164
  // src/init/init-command.ts
8860
- import { existsSync as existsSync22, writeFileSync as writeFileSync8 } from "fs";
8861
- import { join as join14 } from "path";
9165
+ import { existsSync as existsSync23, writeFileSync as writeFileSync8 } from "fs";
9166
+ import { join as join15 } from "path";
8862
9167
  function runProjectInit(options = {}) {
8863
9168
  const cwd = options.cwd || process.cwd();
8864
9169
  const filename = options.filename || "AGENTS.md";
8865
- const targetPath = join14(cwd, filename);
9170
+ const targetPath = join15(cwd, filename);
8866
9171
  const analyzer = new ProjectAnalyzer(cwd);
8867
9172
  const analysis = analyzer.analyze();
8868
9173
  const content = analyzer.generateAgentsMarkdown(analysis);
8869
- const alreadyExists = existsSync22(targetPath);
9174
+ const alreadyExists = existsSync23(targetPath);
8870
9175
  writeFileSync8(targetPath, content, "utf8");
8871
9176
  return {
8872
9177
  success: true,
@@ -10452,9 +10757,9 @@ class MarkdownHighlighter {
10452
10757
  }
10453
10758
 
10454
10759
  // src/cli/update-checker.ts
10455
- import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
10760
+ import { existsSync as existsSync24, mkdirSync as mkdirSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
10456
10761
  import { homedir as homedir4 } from "os";
10457
- import { join as join15 } from "path";
10762
+ import { join as join16 } from "path";
10458
10763
  var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
10459
10764
  function parseSemver(v) {
10460
10765
  const clean = v.replace(/^v/, "").trim();
@@ -10475,8 +10780,8 @@ function isNewerVersion(current, remote) {
10475
10780
  return remPatch > curPatch;
10476
10781
  }
10477
10782
  function getUpdateCachePath() {
10478
- const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join15(homedir4(), ".pikaa");
10479
- return join15(baseDir, "update-cache.json");
10783
+ const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join16(homedir4(), ".pikaa");
10784
+ return join16(baseDir, "update-cache.json");
10480
10785
  }
10481
10786
  async function fetchLatestNpmVersion(packageName, timeoutMs = 1500) {
10482
10787
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
@@ -10509,9 +10814,9 @@ async function checkForUpdates(options = {}) {
10509
10814
  const cachePath = options.cachePath || getUpdateCachePath();
10510
10815
  const now = Date.now();
10511
10816
  let cached = null;
10512
- if (!options.force && existsSync23(cachePath)) {
10817
+ if (!options.force && existsSync24(cachePath)) {
10513
10818
  try {
10514
- const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
10819
+ const raw = JSON.parse(readFileSync15(cachePath, "utf8"));
10515
10820
  if (raw && typeof raw.lastChecked === "number" && typeof raw.latestVersion === "string") {
10516
10821
  cached = raw;
10517
10822
  if (now - cached.lastChecked < CHECK_INTERVAL_MS) {
@@ -10539,8 +10844,8 @@ async function checkForUpdates(options = {}) {
10539
10844
  return null;
10540
10845
  }
10541
10846
  try {
10542
- const parentDir = join15(cachePath, "..");
10543
- if (!existsSync23(parentDir)) {
10847
+ const parentDir = join16(cachePath, "..");
10848
+ if (!existsSync24(parentDir)) {
10544
10849
  mkdirSync13(parentDir, { recursive: true });
10545
10850
  }
10546
10851
  const cacheData = {
@@ -10679,6 +10984,25 @@ class CliRepl {
10679
10984
  formatTaskProgressPlan(msg.plan, msg.explanation);
10680
10985
  this.spinner.start("Executing next step...", this.turnStartTime);
10681
10986
  break;
10987
+ case "VerificationStarted":
10988
+ this.spinner.stop();
10989
+ console.log(style.cyan(`
10990
+ \uD83D\uDD0D Auto-Verification: Running '${msg.command}'...`));
10991
+ this.spinner.start(`Verifying changes with ${msg.command}...`, this.turnStartTime);
10992
+ break;
10993
+ case "VerificationCompleted":
10994
+ this.spinner.stop();
10995
+ if (msg.success) {
10996
+ console.log(style.green(` \u2713 Auto-Verification passed: ${msg.command} (${msg.durationMs ?? 0}ms)`));
10997
+ } else {
10998
+ console.log(style.red(` \u2717 Auto-Verification failed: ${msg.command} (${msg.durationMs ?? 0}ms)`));
10999
+ }
11000
+ break;
11001
+ case "SelfHealingStarted":
11002
+ this.spinner.stop();
11003
+ console.log(style.yellow(` \uD83E\uDE79 Self-Healing Loop triggered (Attempt ${msg.attempt}/${msg.maxAttempts}): Auto-fixing detected errors...`));
11004
+ this.spinner.start(`Self-healing [${msg.attempt}/${msg.maxAttempts}]...`, this.turnStartTime);
11005
+ break;
10682
11006
  case "TurnCompleted":
10683
11007
  if (this.reasoningStarted) {
10684
11008
  console.log(style.dim(`
@@ -10983,7 +11307,7 @@ async function main() {
10983
11307
  resolve22(cwd, "mcp_config.json")
10984
11308
  ].filter(Boolean);
10985
11309
  for (const cfg of candidateConfigs) {
10986
- if (existsSync24(cfg)) {
11310
+ if (existsSync25(cfg)) {
10987
11311
  try {
10988
11312
  await mcpManager.loadConfigFile(cfg);
10989
11313
  mcpManager.registerToolsIntoRouter(tools);