@pikaa-ai/pikaa 0.3.6 → 0.3.7
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 +348 -21
- package/dist/index.js +96 -4
- package/package.json +1 -1
- package/templates/base/groupy_prompt.md +67 -1
package/dist/cli.js
CHANGED
|
@@ -975,6 +975,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
975
975
|
signal,
|
|
976
976
|
execPolicy: session.execPolicy,
|
|
977
977
|
mode: session.collaborationMode,
|
|
978
|
+
permissionMode: session.permissionMode,
|
|
978
979
|
onPlanUpdate: (plan, explanation) => {
|
|
979
980
|
session.emitEvent({
|
|
980
981
|
type: "PlanUpdated",
|
|
@@ -1125,9 +1126,17 @@ async function submissionLoop(session, queue) {
|
|
|
1125
1126
|
// src/security/exec-policy.ts
|
|
1126
1127
|
class ExecPolicy {
|
|
1127
1128
|
rules = [];
|
|
1128
|
-
|
|
1129
|
+
mode = "auto";
|
|
1130
|
+
constructor(initialMode = "auto") {
|
|
1131
|
+
this.mode = initialMode;
|
|
1129
1132
|
this.initDefaultRules();
|
|
1130
1133
|
}
|
|
1134
|
+
getMode() {
|
|
1135
|
+
return this.mode;
|
|
1136
|
+
}
|
|
1137
|
+
setMode(mode) {
|
|
1138
|
+
this.mode = mode;
|
|
1139
|
+
}
|
|
1131
1140
|
initDefaultRules() {
|
|
1132
1141
|
this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
|
|
1133
1142
|
this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
@@ -1139,8 +1148,47 @@ class ExecPolicy {
|
|
|
1139
1148
|
addRule(pattern, decision, description) {
|
|
1140
1149
|
this.rules.unshift({ pattern, decision, description });
|
|
1141
1150
|
}
|
|
1151
|
+
shouldPromptFileEdit(filePath) {
|
|
1152
|
+
if (this.mode === "plan") {
|
|
1153
|
+
return {
|
|
1154
|
+
prompt: false,
|
|
1155
|
+
isPlanBlocked: true,
|
|
1156
|
+
reason: "Plan Mode is active. Mutating files is not allowed while planning."
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
if (this.mode === "manual") {
|
|
1160
|
+
return {
|
|
1161
|
+
prompt: true,
|
|
1162
|
+
reason: `Manual mode requires approval to modify '${filePath || "file"}'`
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
return { prompt: false };
|
|
1166
|
+
}
|
|
1142
1167
|
evaluate(command) {
|
|
1143
1168
|
const trimmed = command.trim();
|
|
1169
|
+
if (this.mode === "plan") {
|
|
1170
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1171
|
+
if (isReadOnly) {
|
|
1172
|
+
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1173
|
+
}
|
|
1174
|
+
return { decision: "deny", reason: "Cannot execute mutating shell commands in Plan mode" };
|
|
1175
|
+
}
|
|
1176
|
+
if (this.mode === "manual") {
|
|
1177
|
+
return {
|
|
1178
|
+
decision: "prompt",
|
|
1179
|
+
reason: "Manual mode requires confirmation for all shell commands"
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
if (this.mode === "accept-edits") {
|
|
1183
|
+
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);
|
|
1184
|
+
if (isReadOnly) {
|
|
1185
|
+
return { decision: "allow", reason: "Safe read-only command in accept-edits mode" };
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
decision: "prompt",
|
|
1189
|
+
reason: "Accept-edits mode requires approval for active shell commands"
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1144
1192
|
for (const rule of this.rules) {
|
|
1145
1193
|
if (rule.pattern.test(trimmed)) {
|
|
1146
1194
|
return {
|
|
@@ -1150,8 +1198,8 @@ class ExecPolicy {
|
|
|
1150
1198
|
}
|
|
1151
1199
|
}
|
|
1152
1200
|
return {
|
|
1153
|
-
decision: "
|
|
1154
|
-
reason: "
|
|
1201
|
+
decision: "allow",
|
|
1202
|
+
reason: "Auto mode allows execution"
|
|
1155
1203
|
};
|
|
1156
1204
|
}
|
|
1157
1205
|
}
|
|
@@ -1169,6 +1217,17 @@ class Session {
|
|
|
1169
1217
|
mcpManager;
|
|
1170
1218
|
execPolicy;
|
|
1171
1219
|
collaborationMode = "default";
|
|
1220
|
+
get permissionMode() {
|
|
1221
|
+
return this.execPolicy.getMode();
|
|
1222
|
+
}
|
|
1223
|
+
setPermissionMode(mode) {
|
|
1224
|
+
this.execPolicy.setMode(mode);
|
|
1225
|
+
if (mode === "plan") {
|
|
1226
|
+
this.collaborationMode = "plan";
|
|
1227
|
+
} else if (this.collaborationMode === "plan") {
|
|
1228
|
+
this.collaborationMode = "default";
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1172
1231
|
history = [];
|
|
1173
1232
|
activeTurn = null;
|
|
1174
1233
|
status = "idle";
|
|
@@ -1415,6 +1474,22 @@ var applyPatchTool = {
|
|
|
1415
1474
|
const filePath = resolve4(ctx.cwd, rawPath);
|
|
1416
1475
|
const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
|
|
1417
1476
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1477
|
+
if (ctx.execPolicy) {
|
|
1478
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
1479
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1480
|
+
return {
|
|
1481
|
+
output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
1482
|
+
isError: true
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
1486
|
+
const approval = await ctx.requestApproval(`Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1487
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
1488
|
+
if (!allowed) {
|
|
1489
|
+
return { output: `Action rejected by user: apply_patch '${rawPath}'`, isError: true };
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1418
1493
|
if (!existsSync4(filePath)) {
|
|
1419
1494
|
if (targetContent) {
|
|
1420
1495
|
return {
|
|
@@ -2049,7 +2124,24 @@ var writeFileTool = {
|
|
|
2049
2124
|
required: ["path", "content"]
|
|
2050
2125
|
},
|
|
2051
2126
|
async execute(args, ctx) {
|
|
2052
|
-
const
|
|
2127
|
+
const rawPath = String(args.path || "");
|
|
2128
|
+
const filePath = resolve7(ctx.cwd, rawPath);
|
|
2129
|
+
if (ctx.execPolicy) {
|
|
2130
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2131
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2132
|
+
return {
|
|
2133
|
+
output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
2134
|
+
isError: true
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
2138
|
+
const approval = await ctx.requestApproval(`Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2139
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
2140
|
+
if (!allowed) {
|
|
2141
|
+
return { output: `Action rejected by user: write_file '${rawPath}'`, isError: true };
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2053
2145
|
try {
|
|
2054
2146
|
mkdirSync4(dirname4(filePath), { recursive: true });
|
|
2055
2147
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
@@ -6003,7 +6095,7 @@ function getPackageMetadata() {
|
|
|
6003
6095
|
} catch {}
|
|
6004
6096
|
cachedMetadata = {
|
|
6005
6097
|
name: "pikaa",
|
|
6006
|
-
version: "0.3.
|
|
6098
|
+
version: "0.3.6"
|
|
6007
6099
|
};
|
|
6008
6100
|
return cachedMetadata;
|
|
6009
6101
|
}
|
|
@@ -6445,10 +6537,41 @@ ${preview}${more}`);
|
|
|
6445
6537
|
console.log();
|
|
6446
6538
|
}
|
|
6447
6539
|
static formatClaudeUserPrompt(text) {
|
|
6448
|
-
const cols = typeof process.stdout?.columns === "number" ? process.stdout.columns : 80;
|
|
6449
|
-
const
|
|
6450
|
-
const
|
|
6451
|
-
|
|
6540
|
+
const cols = typeof process.stdout?.columns === "number" && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
6541
|
+
const boxWidth = Math.max(20, cols - 2);
|
|
6542
|
+
const contentWidth = Math.max(10, boxWidth - 3);
|
|
6543
|
+
const BG = "\x1B[48;2;43;43;45m";
|
|
6544
|
+
const CHEVRON = "\x1B[38;2;128;128;133m";
|
|
6545
|
+
const TEXT = "\x1B[38;2;240;240;242m";
|
|
6546
|
+
const RESET2 = "\x1B[0m";
|
|
6547
|
+
const rawLines = text.split(`
|
|
6548
|
+
`);
|
|
6549
|
+
const wrappedLines = [];
|
|
6550
|
+
for (const rawLine of rawLines) {
|
|
6551
|
+
if (rawLine.length === 0) {
|
|
6552
|
+
wrappedLines.push("");
|
|
6553
|
+
continue;
|
|
6554
|
+
}
|
|
6555
|
+
let current = rawLine;
|
|
6556
|
+
while (current.length > contentWidth) {
|
|
6557
|
+
let breakIdx = current.lastIndexOf(" ", contentWidth);
|
|
6558
|
+
if (breakIdx <= 0) {
|
|
6559
|
+
breakIdx = contentWidth;
|
|
6560
|
+
}
|
|
6561
|
+
wrappedLines.push(current.slice(0, breakIdx));
|
|
6562
|
+
current = current.slice(breakIdx).trimStart();
|
|
6563
|
+
}
|
|
6564
|
+
if (current.length > 0) {
|
|
6565
|
+
wrappedLines.push(current);
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
6568
|
+
const formatted = wrappedLines.map((line, idx) => {
|
|
6569
|
+
const prefix = idx === 0 ? `${CHEVRON} \u276F ` : `${CHEVRON} `;
|
|
6570
|
+
const padLen = Math.max(0, contentWidth - line.length);
|
|
6571
|
+
return ` ${BG}${prefix}${TEXT}${line}${" ".repeat(padLen)}${RESET2}`;
|
|
6572
|
+
});
|
|
6573
|
+
return formatted.join(`
|
|
6574
|
+
`);
|
|
6452
6575
|
}
|
|
6453
6576
|
static formatClaudeAssistantResponse(text) {
|
|
6454
6577
|
return ` \x1B[38;2;192;202;245m${text}\x1B[0m`;
|
|
@@ -6481,12 +6604,49 @@ function ensureKeypressInitialized() {
|
|
|
6481
6604
|
class InteractiveLineEditor {
|
|
6482
6605
|
promptSymbol;
|
|
6483
6606
|
cwd;
|
|
6607
|
+
mode;
|
|
6484
6608
|
onInterrupt;
|
|
6609
|
+
onModeChange;
|
|
6485
6610
|
searchEngine = new FileSearchEngine;
|
|
6486
6611
|
constructor(options = {}) {
|
|
6487
6612
|
this.promptSymbol = options.promptSymbol || " \x1B[38;2;192;202;245m\u276F\x1B[0m ";
|
|
6488
6613
|
this.cwd = options.cwd || process.cwd();
|
|
6614
|
+
this.mode = options.initialMode || "auto";
|
|
6489
6615
|
this.onInterrupt = options.onInterrupt;
|
|
6616
|
+
this.onModeChange = options.onModeChange;
|
|
6617
|
+
}
|
|
6618
|
+
getMode() {
|
|
6619
|
+
return this.mode;
|
|
6620
|
+
}
|
|
6621
|
+
setMode(mode) {
|
|
6622
|
+
this.mode = mode;
|
|
6623
|
+
this.onModeChange?.(mode);
|
|
6624
|
+
}
|
|
6625
|
+
cycleMode() {
|
|
6626
|
+
const modes = ["auto", "manual", "accept-edits", "plan"];
|
|
6627
|
+
const nextIdx = (modes.indexOf(this.mode) + 1) % modes.length;
|
|
6628
|
+
const next = modes[nextIdx];
|
|
6629
|
+
this.setMode(next);
|
|
6630
|
+
return next;
|
|
6631
|
+
}
|
|
6632
|
+
cycleModeReverse() {
|
|
6633
|
+
const modes = ["auto", "manual", "accept-edits", "plan"];
|
|
6634
|
+
const nextIdx = (modes.indexOf(this.mode) - 1 + modes.length) % modes.length;
|
|
6635
|
+
const next = modes[nextIdx];
|
|
6636
|
+
this.setMode(next);
|
|
6637
|
+
return next;
|
|
6638
|
+
}
|
|
6639
|
+
getModeLine() {
|
|
6640
|
+
switch (this.mode) {
|
|
6641
|
+
case "auto":
|
|
6642
|
+
return ` \x1B[38;2;255;215;0m\u23F5\u23F5 auto mode on\x1B[0m \x1B[38;2;148;148;148m(shift+tab to cycle) \xB7 \u21E0 for agents\x1B[0m`;
|
|
6643
|
+
case "manual":
|
|
6644
|
+
return ` \x1B[38;2;148;148;148m\u23F8 manual mode on \xB7 ? for shortcuts \xB7 \u21E0 for agents\x1B[0m`;
|
|
6645
|
+
case "accept-edits":
|
|
6646
|
+
return ` \x1B[38;2;175;175;215m\u23F5\u23F5 accept edits on\x1B[0m \x1B[38;2;148;148;148m(shift+tab to cycle) \xB7 \u21E0 for agents\x1B[0m`;
|
|
6647
|
+
case "plan":
|
|
6648
|
+
return ` \x1B[38;2;95;175;175m\u23F8 plan mode on\x1B[0m \x1B[38;2;148;148;148m(shift+tab to cycle) \xB7 \u21E0 for agents\x1B[0m`;
|
|
6649
|
+
}
|
|
6490
6650
|
}
|
|
6491
6651
|
async readLine() {
|
|
6492
6652
|
if (!process.stdin.isTTY) {
|
|
@@ -6540,6 +6700,14 @@ class InteractiveLineEditor {
|
|
|
6540
6700
|
return [];
|
|
6541
6701
|
}
|
|
6542
6702
|
};
|
|
6703
|
+
const getTerminalCols = () => {
|
|
6704
|
+
return typeof process.stdout?.columns === "number" && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
6705
|
+
};
|
|
6706
|
+
const getRule = () => {
|
|
6707
|
+
const cols = getTerminalCols();
|
|
6708
|
+
const ruleLen = Math.max(10, cols - 4);
|
|
6709
|
+
return "\u2500".repeat(ruleLen);
|
|
6710
|
+
};
|
|
6543
6711
|
const ensureVisible = (totalItems, visibleRows) => {
|
|
6544
6712
|
if (totalItems === 0 || visibleRows === 0) {
|
|
6545
6713
|
scrollTop = 0;
|
|
@@ -6567,37 +6735,39 @@ class InteractiveLineEditor {
|
|
|
6567
6735
|
}
|
|
6568
6736
|
};
|
|
6569
6737
|
const redraw = () => {
|
|
6570
|
-
|
|
6738
|
+
process.stdout.write("\x1B[J");
|
|
6571
6739
|
process.stdout.write(`\r\x1B[2K${this.promptSymbol}${buffer}`);
|
|
6572
6740
|
const slashMatches = getMatchingCommands();
|
|
6573
6741
|
const activeFile = getActiveFileQuery();
|
|
6574
6742
|
const fileMatches = activeFile ? getMatchingFiles(activeFile.query) : [];
|
|
6575
6743
|
if (buffer.startsWith("/") && !popupDismissed && slashMatches.length > 0) {
|
|
6576
|
-
const
|
|
6744
|
+
const termCols = getTerminalCols();
|
|
6745
|
+
const BOX_WIDTH = Math.max(20, Math.min(termCols - 4, 120));
|
|
6577
6746
|
const maxVisible = Math.min(slashMatches.length, 7);
|
|
6578
6747
|
ensureVisible(slashMatches.length, maxVisible);
|
|
6579
6748
|
const visibleMatches = slashMatches.slice(scrollTop, scrollTop + maxVisible);
|
|
6580
6749
|
const menuLines = [];
|
|
6581
|
-
const rule = "\u2500".repeat(
|
|
6582
|
-
const
|
|
6750
|
+
const rule = "\u2500".repeat(BOX_WIDTH);
|
|
6751
|
+
const RULE_COLOR2 = "\x1B[38;2;80;80;88m";
|
|
6583
6752
|
const ACTIVE_COLOR = "\x1B[38;2;225;225;225m";
|
|
6584
6753
|
const INACTIVE_COLOR = "\x1B[38;2;139;139;144m";
|
|
6585
6754
|
const RESET2 = "\x1B[0m";
|
|
6586
|
-
menuLines.push(` ${
|
|
6755
|
+
menuLines.push(` ${RULE_COLOR2}${rule}${RESET2}`);
|
|
6756
|
+
const maxDescLen = Math.max(10, BOX_WIDTH - 22);
|
|
6587
6757
|
for (let i = 0;i < visibleMatches.length; i++) {
|
|
6588
6758
|
const cmd = visibleMatches[i];
|
|
6589
6759
|
const actualIdx = scrollTop + i;
|
|
6590
6760
|
const isSelected = actualIdx === selectedIndex;
|
|
6591
6761
|
const marker = isSelected ? `${ACTIVE_COLOR}\u276F${RESET2}` : " ";
|
|
6592
6762
|
const rawName = cmd.name.padEnd(16).slice(0, 16);
|
|
6593
|
-
const rawDesc = cmd.description.length >
|
|
6763
|
+
const rawDesc = cmd.description.length > maxDescLen ? cmd.description.slice(0, maxDescLen - 3) + "..." : cmd.description;
|
|
6594
6764
|
if (isSelected) {
|
|
6595
6765
|
menuLines.push(` ${marker} \x1B[1m${ACTIVE_COLOR}${rawName}${RESET2} \x1B[1m${ACTIVE_COLOR}${rawDesc}${RESET2}`);
|
|
6596
6766
|
} else {
|
|
6597
6767
|
menuLines.push(` ${marker} ${INACTIVE_COLOR}${rawName}${RESET2} ${INACTIVE_COLOR}${rawDesc}${RESET2}`);
|
|
6598
6768
|
}
|
|
6599
6769
|
}
|
|
6600
|
-
menuLines.push(` ${
|
|
6770
|
+
menuLines.push(` ${RULE_COLOR2}${rule}${RESET2}`);
|
|
6601
6771
|
for (const line of menuLines) {
|
|
6602
6772
|
process.stdout.write(`
|
|
6603
6773
|
\x1B[2K${line}`);
|
|
@@ -6605,18 +6775,20 @@ class InteractiveLineEditor {
|
|
|
6605
6775
|
renderedMenuLines = menuLines.length;
|
|
6606
6776
|
process.stdout.write(`\x1B[${renderedMenuLines}A`);
|
|
6607
6777
|
} else if (activeFile && !popupDismissed && fileMatches.length > 0) {
|
|
6608
|
-
const
|
|
6778
|
+
const termCols = getTerminalCols();
|
|
6779
|
+
const BOX_WIDTH = Math.max(20, Math.min(termCols - 4, 120));
|
|
6609
6780
|
const maxVisible = Math.min(fileMatches.length, 7);
|
|
6610
6781
|
ensureVisible(fileMatches.length, maxVisible);
|
|
6611
6782
|
const visibleMatches = fileMatches.slice(scrollTop, scrollTop + maxVisible);
|
|
6612
6783
|
const menuLines = [];
|
|
6613
6784
|
menuLines.push(` ${style.dim("\u250C\u2500\u2500")} ${style.brandBold("Files")} ${style.dim("\u2500".repeat(Math.max(10, BOX_WIDTH - 9)) + "\u2510")}`);
|
|
6785
|
+
const maxPathLen = Math.max(10, BOX_WIDTH - 8);
|
|
6614
6786
|
for (let i = 0;i < visibleMatches.length; i++) {
|
|
6615
6787
|
const filePath = visibleMatches[i];
|
|
6616
6788
|
const actualIdx = scrollTop + i;
|
|
6617
6789
|
const isSelected = actualIdx === selectedIndex;
|
|
6618
6790
|
const marker = isSelected ? style.brand("\u276F") : " ";
|
|
6619
|
-
const rawPath = filePath.length >
|
|
6791
|
+
const rawPath = filePath.length > maxPathLen ? "..." + filePath.slice(filePath.length - (maxPathLen - 3)) : filePath.padEnd(maxPathLen);
|
|
6620
6792
|
const coloredPath = isSelected ? style.brandBold(rawPath) : style.cyan(rawPath);
|
|
6621
6793
|
menuLines.push(` ${style.dim("\u2502")} ${marker} ${coloredPath} ${style.dim("\u2502")}`);
|
|
6622
6794
|
}
|
|
@@ -6641,6 +6813,15 @@ class InteractiveLineEditor {
|
|
|
6641
6813
|
}
|
|
6642
6814
|
renderedMenuLines = menuLines.length;
|
|
6643
6815
|
process.stdout.write(`\x1B[${renderedMenuLines}A`);
|
|
6816
|
+
} else {
|
|
6817
|
+
const RULE_COLOR2 = "\x1B[38;2;60;60;68m";
|
|
6818
|
+
const bottomRule = ` ${RULE_COLOR2}${getRule()}\x1B[0m`;
|
|
6819
|
+
const modeLine = this.getModeLine();
|
|
6820
|
+
process.stdout.write(`
|
|
6821
|
+
\x1B[2K${bottomRule}
|
|
6822
|
+
\x1B[2K${modeLine}`);
|
|
6823
|
+
renderedMenuLines = 2;
|
|
6824
|
+
process.stdout.write(`\x1B[2A`);
|
|
6644
6825
|
}
|
|
6645
6826
|
const visiblePromptLength = this.promptSymbol.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
6646
6827
|
const cursorCol = visiblePromptLength + cursor;
|
|
@@ -6650,7 +6831,16 @@ class InteractiveLineEditor {
|
|
|
6650
6831
|
process.stdout.write("\r");
|
|
6651
6832
|
}
|
|
6652
6833
|
};
|
|
6834
|
+
const onResize = () => {
|
|
6835
|
+
redraw();
|
|
6836
|
+
};
|
|
6837
|
+
if (process.stdout && typeof process.stdout.on === "function") {
|
|
6838
|
+
process.stdout.on("resize", onResize);
|
|
6839
|
+
}
|
|
6653
6840
|
const cleanupAndResolve = (result) => {
|
|
6841
|
+
if (process.stdout && typeof process.stdout.removeListener === "function") {
|
|
6842
|
+
process.stdout.removeListener("resize", onResize);
|
|
6843
|
+
}
|
|
6654
6844
|
clearMenu();
|
|
6655
6845
|
process.stdin.removeListener("keypress", onKeypress);
|
|
6656
6846
|
if (process.stdin.isTTY) {
|
|
@@ -6659,11 +6849,11 @@ class InteractiveLineEditor {
|
|
|
6659
6849
|
} catch {}
|
|
6660
6850
|
}
|
|
6661
6851
|
if (result.trim().length > 0 && !result.startsWith("/")) {
|
|
6662
|
-
process.stdout.write(`\r\x1B[2K${CliFormatter.formatClaudeUserPrompt(result)}
|
|
6852
|
+
process.stdout.write(`\x1B[1A\r\x1B[2K${CliFormatter.formatClaudeUserPrompt(result)}
|
|
6663
6853
|
|
|
6664
6854
|
`);
|
|
6665
6855
|
} else {
|
|
6666
|
-
process.stdout.write(
|
|
6856
|
+
process.stdout.write(`\x1B[1A\r\x1B[2K
|
|
6667
6857
|
`);
|
|
6668
6858
|
}
|
|
6669
6859
|
resolve18(result);
|
|
@@ -6681,6 +6871,9 @@ class InteractiveLineEditor {
|
|
|
6681
6871
|
redraw();
|
|
6682
6872
|
return;
|
|
6683
6873
|
}
|
|
6874
|
+
if (process.stdout && typeof process.stdout.removeListener === "function") {
|
|
6875
|
+
process.stdout.removeListener("resize", onResize);
|
|
6876
|
+
}
|
|
6684
6877
|
clearMenu();
|
|
6685
6878
|
if (process.stdin.isTTY) {
|
|
6686
6879
|
try {
|
|
@@ -6731,6 +6924,12 @@ class InteractiveLineEditor {
|
|
|
6731
6924
|
cleanupAndResolve(buffer);
|
|
6732
6925
|
return;
|
|
6733
6926
|
}
|
|
6927
|
+
const isShiftTab = key.name === "tab" && Boolean(key.shift) || key.name === "backtab" || key.sequence === "\x1B[Z" || _str === "\x1B[Z";
|
|
6928
|
+
if (isShiftTab) {
|
|
6929
|
+
this.cycleMode();
|
|
6930
|
+
redraw();
|
|
6931
|
+
return;
|
|
6932
|
+
}
|
|
6734
6933
|
if (key.name === "tab") {
|
|
6735
6934
|
if (activeFile && !popupDismissed && fileMatches.length > 0) {
|
|
6736
6935
|
const chosen = fileMatches[selectedIndex] || fileMatches[0];
|
|
@@ -6753,8 +6952,14 @@ class InteractiveLineEditor {
|
|
|
6753
6952
|
scrollTop = 0;
|
|
6754
6953
|
popupDismissed = false;
|
|
6755
6954
|
redraw();
|
|
6955
|
+
return;
|
|
6756
6956
|
}
|
|
6757
6957
|
}
|
|
6958
|
+
if (buffer.trim().length === 0) {
|
|
6959
|
+
this.cycleMode();
|
|
6960
|
+
redraw();
|
|
6961
|
+
return;
|
|
6962
|
+
}
|
|
6758
6963
|
return;
|
|
6759
6964
|
}
|
|
6760
6965
|
if (key.name === "up") {
|
|
@@ -6818,7 +7023,11 @@ class InteractiveLineEditor {
|
|
|
6818
7023
|
}
|
|
6819
7024
|
};
|
|
6820
7025
|
process.stdin.on("keypress", onKeypress);
|
|
6821
|
-
|
|
7026
|
+
const RULE_COLOR = "\x1B[38;2;60;60;68m";
|
|
7027
|
+
process.stdout.write(`
|
|
7028
|
+
${RULE_COLOR}${getRule()}\x1B[0m
|
|
7029
|
+
`);
|
|
7030
|
+
redraw();
|
|
6822
7031
|
});
|
|
6823
7032
|
}
|
|
6824
7033
|
}
|
|
@@ -7550,6 +7759,11 @@ var AVAILABLE_SLASH_COMMANDS = [
|
|
|
7550
7759
|
{ name: "/reasoning", description: "Toggle internal reasoning chain visibility" },
|
|
7551
7760
|
{ name: "/login", description: "Authenticate with backend provider" },
|
|
7552
7761
|
{ name: "/whoami", description: "Check backend authentication status" },
|
|
7762
|
+
{ name: "/mode", description: "Cycle or set execution permission mode (auto, manual, accept-edits, plan)" },
|
|
7763
|
+
{ name: "/auto", description: "Switch to Auto Mode (tools execute automatically)" },
|
|
7764
|
+
{ name: "/manual", description: "Switch to Manual Mode (all tools require user confirmation)" },
|
|
7765
|
+
{ name: "/accept-edits", description: "Switch to Accept Edits Mode (file edits auto-approved, shell prompts)" },
|
|
7766
|
+
{ name: "/plan", description: "Switch to Plan Mode (read-only planning, mutations blocked)" },
|
|
7553
7767
|
{ name: "/skills", description: "List domain skills in workspace & global" },
|
|
7554
7768
|
{ name: "/memories", description: "View learned preferences & memories" },
|
|
7555
7769
|
{ name: "/worktrees", description: "List active isolated Git Worktrees" },
|
|
@@ -7601,6 +7815,37 @@ async function handleSlashCommand(input, ctx) {
|
|
|
7601
7815
|
case "/roles":
|
|
7602
7816
|
await handleRolesCommand(ctx, args[0]);
|
|
7603
7817
|
return true;
|
|
7818
|
+
case "/mode":
|
|
7819
|
+
case "/modes":
|
|
7820
|
+
case "/permission":
|
|
7821
|
+
case "/permissions":
|
|
7822
|
+
await handleModeCommand(ctx, args[0]);
|
|
7823
|
+
return true;
|
|
7824
|
+
case "/auto":
|
|
7825
|
+
ctx.session.setPermissionMode("auto");
|
|
7826
|
+
console.log(`
|
|
7827
|
+
\x1B[38;2;255;215;0m\u23F5\u23F5 Switched to Auto Mode (tools execute automatically)\x1B[0m
|
|
7828
|
+
`);
|
|
7829
|
+
return true;
|
|
7830
|
+
case "/manual":
|
|
7831
|
+
ctx.session.setPermissionMode("manual");
|
|
7832
|
+
console.log(`
|
|
7833
|
+
\x1B[38;2;148;148;148m\u23F8 Switched to Manual Mode (all tools require user confirmation)\x1B[0m
|
|
7834
|
+
`);
|
|
7835
|
+
return true;
|
|
7836
|
+
case "/accept-edits":
|
|
7837
|
+
case "/acceptedits":
|
|
7838
|
+
ctx.session.setPermissionMode("accept-edits");
|
|
7839
|
+
console.log(`
|
|
7840
|
+
\x1B[38;2;175;175;215m\u23F5\u23F5 Switched to Accept Edits Mode (file edits auto-approved, shell prompts)\x1B[0m
|
|
7841
|
+
`);
|
|
7842
|
+
return true;
|
|
7843
|
+
case "/plan":
|
|
7844
|
+
ctx.session.setPermissionMode("plan");
|
|
7845
|
+
console.log(`
|
|
7846
|
+
\x1B[38;2;95;175;175m\u23F8 Switched to Plan Mode (read-only planning, mutations blocked)\x1B[0m
|
|
7847
|
+
`);
|
|
7848
|
+
return true;
|
|
7604
7849
|
case "/agents":
|
|
7605
7850
|
printAgents(ctx);
|
|
7606
7851
|
return true;
|
|
@@ -8517,6 +8762,84 @@ function printReleaseNotes() {
|
|
|
8517
8762
|
console.log(" " + ROSE2 + "\u2514" + "\u2500".repeat(totalInnerWidth) + "\u2518" + RESET2);
|
|
8518
8763
|
console.log("");
|
|
8519
8764
|
}
|
|
8765
|
+
async function handleModeCommand(ctx, arg) {
|
|
8766
|
+
const validModes = [
|
|
8767
|
+
{
|
|
8768
|
+
mode: "auto",
|
|
8769
|
+
title: "Auto Mode",
|
|
8770
|
+
desc: "Tools run automatically without confirmation (fastest autonomous workflow)",
|
|
8771
|
+
glyph: "\u23F5\u23F5",
|
|
8772
|
+
color: "\x1B[38;2;255;215;0m"
|
|
8773
|
+
},
|
|
8774
|
+
{
|
|
8775
|
+
mode: "manual",
|
|
8776
|
+
title: "Manual Mode",
|
|
8777
|
+
desc: "Every file edit and shell command asks for user confirmation",
|
|
8778
|
+
glyph: "\u23F8",
|
|
8779
|
+
color: "\x1B[38;2;148;148;148m"
|
|
8780
|
+
},
|
|
8781
|
+
{
|
|
8782
|
+
mode: "accept-edits",
|
|
8783
|
+
title: "Accept Edits Mode",
|
|
8784
|
+
desc: "File edits are auto-approved, but shell commands require approval",
|
|
8785
|
+
glyph: "\u23F5\u23F5",
|
|
8786
|
+
color: "\x1B[38;2;175;175;215m"
|
|
8787
|
+
},
|
|
8788
|
+
{
|
|
8789
|
+
mode: "plan",
|
|
8790
|
+
title: "Plan Mode",
|
|
8791
|
+
desc: "Read-only mode. Blocks all mutations and focuses strictly on planning",
|
|
8792
|
+
glyph: "\u23F8",
|
|
8793
|
+
color: "\x1B[38;2;95;175;175m"
|
|
8794
|
+
}
|
|
8795
|
+
];
|
|
8796
|
+
const currentMode = ctx.session.permissionMode;
|
|
8797
|
+
if (arg) {
|
|
8798
|
+
const normalized = arg.trim().toLowerCase();
|
|
8799
|
+
const match = validModes.find((m) => m.mode === normalized || m.mode.replace("-", "") === normalized);
|
|
8800
|
+
if (match) {
|
|
8801
|
+
ctx.session.setPermissionMode(match.mode);
|
|
8802
|
+
console.log(`
|
|
8803
|
+
${match.color}${match.glyph} Switched to ${match.title}\x1B[0m
|
|
8804
|
+
${style.dim(match.desc)}
|
|
8805
|
+
`);
|
|
8806
|
+
return;
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
if (!process.stdin.isTTY || false || !process.stdin.readable) {
|
|
8810
|
+
console.log(`
|
|
8811
|
+
Current Permission Mode: ${style.bold(currentMode)}`);
|
|
8812
|
+
for (const m of validModes) {
|
|
8813
|
+
const active = m.mode === currentMode ? " (active)" : "";
|
|
8814
|
+
console.log(` \u2022 ${m.title}${active}: ${m.desc}`);
|
|
8815
|
+
}
|
|
8816
|
+
console.log();
|
|
8817
|
+
return;
|
|
8818
|
+
}
|
|
8819
|
+
const items = validModes.map((m) => ({
|
|
8820
|
+
id: m.mode,
|
|
8821
|
+
label: `${m.glyph} ${m.title}`,
|
|
8822
|
+
badge: m.mode === currentMode ? "ACTIVE" : undefined,
|
|
8823
|
+
description: m.desc,
|
|
8824
|
+
checked: m.mode === currentMode
|
|
8825
|
+
}));
|
|
8826
|
+
const res = await promptInteractiveList({
|
|
8827
|
+
title: "\uD83C\uDF9B\uFE0F Select Execution Permission Mode (Shift+Tab in prompt to cycle)",
|
|
8828
|
+
items,
|
|
8829
|
+
mode: "select",
|
|
8830
|
+
customKeyHints: "\u2191/\u2193: navigate \xB7 Enter: switch mode \xB7 Esc: cancel"
|
|
8831
|
+
});
|
|
8832
|
+
if (res.action === "select" && res.selectedItem) {
|
|
8833
|
+
const chosen = validModes.find((m) => m.mode === res.selectedItem?.id);
|
|
8834
|
+
if (chosen) {
|
|
8835
|
+
ctx.session.setPermissionMode(chosen.mode);
|
|
8836
|
+
console.log(`
|
|
8837
|
+
${chosen.color}${chosen.glyph} Switched to ${chosen.title}\x1B[0m
|
|
8838
|
+
${style.dim(chosen.desc)}
|
|
8839
|
+
`);
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
}
|
|
8520
8843
|
|
|
8521
8844
|
// src/cli/ui/markdown.ts
|
|
8522
8845
|
class MarkdownHighlighter {
|
|
@@ -9124,6 +9447,10 @@ class CliRepl {
|
|
|
9124
9447
|
}).catch(() => {});
|
|
9125
9448
|
const editor = new InteractiveLineEditor({
|
|
9126
9449
|
cwd: this.session.cwd,
|
|
9450
|
+
initialMode: this.session.permissionMode,
|
|
9451
|
+
onModeChange: (newMode) => {
|
|
9452
|
+
this.session.setPermissionMode(newMode);
|
|
9453
|
+
},
|
|
9127
9454
|
onInterrupt: () => {
|
|
9128
9455
|
if (this.isProcessing) {
|
|
9129
9456
|
const activeTurn = this.session.getActiveTurn();
|
package/dist/index.js
CHANGED
|
@@ -1648,6 +1648,22 @@ var applyPatchTool = {
|
|
|
1648
1648
|
const filePath = resolve2(ctx.cwd, rawPath);
|
|
1649
1649
|
const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
|
|
1650
1650
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1651
|
+
if (ctx.execPolicy) {
|
|
1652
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
1653
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1654
|
+
return {
|
|
1655
|
+
output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
1656
|
+
isError: true
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
1660
|
+
const approval = await ctx.requestApproval(`Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1661
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
1662
|
+
if (!allowed) {
|
|
1663
|
+
return { output: `Action rejected by user: apply_patch '${rawPath}'`, isError: true };
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1651
1667
|
if (!existsSync2(filePath)) {
|
|
1652
1668
|
if (targetContent) {
|
|
1653
1669
|
return {
|
|
@@ -1704,9 +1720,17 @@ var applyPatchTool = {
|
|
|
1704
1720
|
// src/security/exec-policy.ts
|
|
1705
1721
|
class ExecPolicy {
|
|
1706
1722
|
rules = [];
|
|
1707
|
-
|
|
1723
|
+
mode = "auto";
|
|
1724
|
+
constructor(initialMode = "auto") {
|
|
1725
|
+
this.mode = initialMode;
|
|
1708
1726
|
this.initDefaultRules();
|
|
1709
1727
|
}
|
|
1728
|
+
getMode() {
|
|
1729
|
+
return this.mode;
|
|
1730
|
+
}
|
|
1731
|
+
setMode(mode) {
|
|
1732
|
+
this.mode = mode;
|
|
1733
|
+
}
|
|
1710
1734
|
initDefaultRules() {
|
|
1711
1735
|
this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
|
|
1712
1736
|
this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
@@ -1718,8 +1742,47 @@ class ExecPolicy {
|
|
|
1718
1742
|
addRule(pattern, decision, description) {
|
|
1719
1743
|
this.rules.unshift({ pattern, decision, description });
|
|
1720
1744
|
}
|
|
1745
|
+
shouldPromptFileEdit(filePath) {
|
|
1746
|
+
if (this.mode === "plan") {
|
|
1747
|
+
return {
|
|
1748
|
+
prompt: false,
|
|
1749
|
+
isPlanBlocked: true,
|
|
1750
|
+
reason: "Plan Mode is active. Mutating files is not allowed while planning."
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
if (this.mode === "manual") {
|
|
1754
|
+
return {
|
|
1755
|
+
prompt: true,
|
|
1756
|
+
reason: `Manual mode requires approval to modify '${filePath || "file"}'`
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
return { prompt: false };
|
|
1760
|
+
}
|
|
1721
1761
|
evaluate(command) {
|
|
1722
1762
|
const trimmed = command.trim();
|
|
1763
|
+
if (this.mode === "plan") {
|
|
1764
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1765
|
+
if (isReadOnly) {
|
|
1766
|
+
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1767
|
+
}
|
|
1768
|
+
return { decision: "deny", reason: "Cannot execute mutating shell commands in Plan mode" };
|
|
1769
|
+
}
|
|
1770
|
+
if (this.mode === "manual") {
|
|
1771
|
+
return {
|
|
1772
|
+
decision: "prompt",
|
|
1773
|
+
reason: "Manual mode requires confirmation for all shell commands"
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
if (this.mode === "accept-edits") {
|
|
1777
|
+
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);
|
|
1778
|
+
if (isReadOnly) {
|
|
1779
|
+
return { decision: "allow", reason: "Safe read-only command in accept-edits mode" };
|
|
1780
|
+
}
|
|
1781
|
+
return {
|
|
1782
|
+
decision: "prompt",
|
|
1783
|
+
reason: "Accept-edits mode requires approval for active shell commands"
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1723
1786
|
for (const rule of this.rules) {
|
|
1724
1787
|
if (rule.pattern.test(trimmed)) {
|
|
1725
1788
|
return {
|
|
@@ -1729,8 +1792,8 @@ class ExecPolicy {
|
|
|
1729
1792
|
}
|
|
1730
1793
|
}
|
|
1731
1794
|
return {
|
|
1732
|
-
decision: "
|
|
1733
|
-
reason: "
|
|
1795
|
+
decision: "allow",
|
|
1796
|
+
reason: "Auto mode allows execution"
|
|
1734
1797
|
};
|
|
1735
1798
|
}
|
|
1736
1799
|
}
|
|
@@ -2316,7 +2379,24 @@ var writeFileTool = {
|
|
|
2316
2379
|
required: ["path", "content"]
|
|
2317
2380
|
},
|
|
2318
2381
|
async execute(args, ctx) {
|
|
2319
|
-
const
|
|
2382
|
+
const rawPath = String(args.path || "");
|
|
2383
|
+
const filePath = resolve5(ctx.cwd, rawPath);
|
|
2384
|
+
if (ctx.execPolicy) {
|
|
2385
|
+
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2386
|
+
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2387
|
+
return {
|
|
2388
|
+
output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
2389
|
+
isError: true
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
if (evalResult.prompt && ctx.requestApproval) {
|
|
2393
|
+
const approval = await ctx.requestApproval(`Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2394
|
+
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
2395
|
+
if (!allowed) {
|
|
2396
|
+
return { output: `Action rejected by user: write_file '${rawPath}'`, isError: true };
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2320
2400
|
try {
|
|
2321
2401
|
mkdirSync4(dirname3(filePath), { recursive: true });
|
|
2322
2402
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
@@ -3881,6 +3961,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
3881
3961
|
signal,
|
|
3882
3962
|
execPolicy: session.execPolicy,
|
|
3883
3963
|
mode: session.collaborationMode,
|
|
3964
|
+
permissionMode: session.permissionMode,
|
|
3884
3965
|
onPlanUpdate: (plan2, explanation) => {
|
|
3885
3966
|
session.emitEvent({
|
|
3886
3967
|
type: "PlanUpdated",
|
|
@@ -4041,6 +4122,17 @@ class Session {
|
|
|
4041
4122
|
mcpManager;
|
|
4042
4123
|
execPolicy;
|
|
4043
4124
|
collaborationMode = "default";
|
|
4125
|
+
get permissionMode() {
|
|
4126
|
+
return this.execPolicy.getMode();
|
|
4127
|
+
}
|
|
4128
|
+
setPermissionMode(mode) {
|
|
4129
|
+
this.execPolicy.setMode(mode);
|
|
4130
|
+
if (mode === "plan") {
|
|
4131
|
+
this.collaborationMode = "plan";
|
|
4132
|
+
} else if (this.collaborationMode === "plan") {
|
|
4133
|
+
this.collaborationMode = "default";
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4044
4136
|
history = [];
|
|
4045
4137
|
activeTurn = null;
|
|
4046
4138
|
status = "idle";
|
package/package.json
CHANGED
|
@@ -15,9 +15,75 @@ You are Groupy, an expert autonomous AI coding assistant. You are running as a c
|
|
|
15
15
|
* If the changes are in files you've touched recently, you should read carefully and understand how you can work with the changes rather than reverting them.
|
|
16
16
|
* If the changes are in unrelated files, just ignore them and don't revert them.
|
|
17
17
|
- Do not amend a commit unless explicitly requested to do so.
|
|
18
|
-
- While you are working, you might notice unexpected changes that you didn't make. If this happens, STOP IMMEDIATELY and ask the user how they would like to proceed.
|
|
19
18
|
- **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user.
|
|
20
19
|
|
|
20
|
+
## Adaptive Task Routing (Automatic Complexity Detection)
|
|
21
|
+
|
|
22
|
+
1. **Small / Standard Tasks (Single component, straightforward, < 3 files)**:
|
|
23
|
+
- Proceed directly to execution with zero overhead. Do not create heavy plans or ask trivial confirmation questions.
|
|
24
|
+
- Code surgically and verify with automated tests.
|
|
25
|
+
|
|
26
|
+
2. **Large / Complex / Ambiguous Tasks (> 3 files, architectural overhaul, new subsystem)**:
|
|
27
|
+
- **Step 1 (Auto-Plan)**: Outline a brief step-by-step Implementation Plan before modifying files.
|
|
28
|
+
- **Step 2 (Clarify Ambiguity & Present Choices)**: If there are architectural trade-offs or underspecified requirements, pause and present numbered choices with Option 1 marked as `(Recommended)`.
|
|
29
|
+
- **Step 3 (Execute & Delegate)**: Once the user confirms, execute systematically. Autonomously spawn specialized sub-agents (`spawn_agent`) for independent sub-tasks (e.g. `security-auditor`, `frontend-designer`, `tester`, `researcher`).
|
|
30
|
+
- **Step 4 (Verify)**: Run full build and test suites, automatically repairing any failures before concluding.
|
|
31
|
+
|
|
32
|
+
## Clarifications, Decision Branching & Recommendations
|
|
33
|
+
|
|
34
|
+
1. **Avoid Trivial Questions**:
|
|
35
|
+
- For routine decisions (naming, syntax, sensible standard defaults), use industry best practices and proceed autonomously without bothering the user.
|
|
36
|
+
|
|
37
|
+
2. **When to Pause & Clarify (Ambiguity & Trade-offs)**:
|
|
38
|
+
- Pause and ask if you encounter:
|
|
39
|
+
* Significantly underspecified requirements (e.g., storage driver, auth strategy, deployment target).
|
|
40
|
+
* Potential breaking changes affecting existing modules.
|
|
41
|
+
* Large architectural choices with distinct trade-offs.
|
|
42
|
+
|
|
43
|
+
3. **Recommendation Format**:
|
|
44
|
+
- Always format choices as clear, numbered options (`1.`, `2.`, `3.`).
|
|
45
|
+
- Place your best technical recommendation as Option 1 prefixed with `(Recommended)`.
|
|
46
|
+
- Keep options concise so the user can reply instantly with a single number.
|
|
47
|
+
|
|
48
|
+
## Codebase Discovery & Execution Strategy
|
|
49
|
+
|
|
50
|
+
1. **Broad Exploration ("pelajari project ini / repo ini tentang apa")**:
|
|
51
|
+
- Inspect ONLY root configs (`package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`, etc.), `README.md`, and top-level directory tree.
|
|
52
|
+
- Deliver a concise Architecture Overview (Tech stack, folder hierarchy, main entry points).
|
|
53
|
+
- Do NOT read full component or implementation files during initial reconnaissance. Stop and ask the user what to build or investigate next.
|
|
54
|
+
|
|
55
|
+
2. **Direct Feature Requests ("buat fitur X")**:
|
|
56
|
+
- Do NOT scan or read unrelated files across the repo.
|
|
57
|
+
- Use `grep_search` or `find_files` to pinpoint the exact target area.
|
|
58
|
+
- Read ONLY 1-2 existing reference files to understand established patterns, naming conventions, and shared utilities (avoid reinventing the wheel).
|
|
59
|
+
- Implement the minimal, clean, and robust code needed to satisfy the request.
|
|
60
|
+
|
|
61
|
+
3. **Bug Fixes & Diagnostics ("kenapa error X / perbaiki bug Y")**:
|
|
62
|
+
- Trace from the reported symptom using `grep_search` to find all callers and the shared function.
|
|
63
|
+
- Fix the root cause in the shared module once, rather than applying band-aid patches across callers.
|
|
64
|
+
|
|
65
|
+
## Mandatory Verification, Testing & Self-Repair Loop
|
|
66
|
+
|
|
67
|
+
Before considering any task complete, you MUST execute the following verification steps:
|
|
68
|
+
|
|
69
|
+
1. **Write Automated Tests**:
|
|
70
|
+
- For any non-trivial logic, new feature, or bug fix, write a clean and targeted test suite (or update existing tests).
|
|
71
|
+
- Ensure the test covers edge cases and specifically verifies that the bug cannot regress.
|
|
72
|
+
|
|
73
|
+
2. **Run Build & Test Validation (Platform & Stack Specific)**:
|
|
74
|
+
Detect the project type and execute the appropriate validation command via terminal:
|
|
75
|
+
- **TypeScript / JavaScript (Node, Bun, Deno)**: Run `bun test` / `npm test`, `tsc --noEmit`, or `npm run build`.
|
|
76
|
+
- **Rust**: Run `cargo check`, `cargo test`, and `cargo build`.
|
|
77
|
+
- **Go**: Run `go test ./...` and `go build`.
|
|
78
|
+
- **Python**: Run `pytest` or `python -m unittest`, and verify syntax with `python -m py_compile <files>` or `mypy`.
|
|
79
|
+
- **Java / Kotlin**: Run `./gradlew test` / `mvn test` and verify compile.
|
|
80
|
+
- **C / C++ / C#**: Run project build / test targets (`dotnet test`, `cmake --build`, `make test`).
|
|
81
|
+
|
|
82
|
+
3. **Autonomous Self-Repair (Do Not Stop on Error)**:
|
|
83
|
+
- If tests fail, types mismatch, or the build produces compilation errors, do NOT stop and report failure immediately.
|
|
84
|
+
- Inspect the compiler/runtime stack trace, diagnose the exact failure, apply the fix, and re-run verification until all checks pass cleanly.
|
|
85
|
+
- Only conclude your turn once the code compiles, builds, and passes all tests.
|
|
86
|
+
|
|
21
87
|
## Skills & Autonomous Domain Knowledge
|
|
22
88
|
|
|
23
89
|
You have access to specialized domain skills listed in `<available_skills>`.
|