@lazyingart/agintiflow 0.20.44 → 0.20.46
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/README.md +5 -1
- package/package.json +1 -1
- package/scripts/smoke-cli-chat.js +26 -1
- package/skills/code-review/SKILL.md +8 -1
- package/src/cli.js +16 -8
- package/src/docker-sandbox.js +25 -3
- package/src/interactive-cli.js +66 -2
- package/src/task-profiles.js +14 -0
package/README.md
CHANGED
|
@@ -146,6 +146,8 @@ aginti resume --all-sessions
|
|
|
146
146
|
aginti resume latest
|
|
147
147
|
aginti resume <session-id> "continue with a short follow-up"
|
|
148
148
|
aginti queue <session-id> "extra instruction for the running agent"
|
|
149
|
+
aginti chat
|
|
150
|
+
# then in chat: /review [focus]
|
|
149
151
|
aginti --profile code "write a small Python CLI app with tests"
|
|
150
152
|
aginti --latex "draw a figure, write a short LaTeX report, and compile the PDF"
|
|
151
153
|
aginti "set up this project and run the tests"
|
|
@@ -153,7 +155,9 @@ aginti "set up this project and run the tests"
|
|
|
153
155
|
|
|
154
156
|
Bare `aginti resume` lists sessions for the current cwd by default. Use `--all-sessions` to browse the global session index; in the interactive selector, type a number to resume, `q` to quit, `/text` to filter the visible list, or `/` to clear the filter.
|
|
155
157
|
|
|
156
|
-
Session cleanup is cwd-scoped by default. `aginti --remove-empty-sessions` shows only empty sessions and preselects them; `aginti --remove-sessions` shows all cwd sessions with nothing preselected. The cleanup selector uses Space to select, Up/Down to move, Tab to switch to
|
|
158
|
+
Session cleanup is cwd-scoped by default. `aginti --remove-empty-sessions` shows only empty sessions and preselects them; `aginti --remove-sessions` shows all cwd sessions with nothing preselected. The cleanup selector uses Space to select or activate the focused button, Up/Down to move, Tab to switch to Delete/Cancel, and a second Delete/Cancel confirmation before deleting the project pointer and central `~/.agintiflow/sessions/<session-id>` data.
|
|
159
|
+
|
|
160
|
+
In interactive chat, `/review [focus]` starts a bounded repository review. It begins from git status/diff and project instructions, reads manifests/entry points/tests/changed files first, avoids generated or binary folders, limits discovery passes, and reports findings before any summary.
|
|
157
161
|
|
|
158
162
|
Run from a source checkout:
|
|
159
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.46",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
buildPromptRenderSequence,
|
|
12
12
|
canonicalSlashPromptBuffer,
|
|
13
13
|
classifyEscapeAction,
|
|
14
|
+
formatElapsedDuration,
|
|
14
15
|
formatWorkspaceChange,
|
|
15
16
|
stripMarkdown,
|
|
16
17
|
} from "../src/interactive-cli.js";
|
|
18
|
+
import { dockerPolicyTimeoutMs, dockerUserCommand } from "../src/docker-sandbox.js";
|
|
17
19
|
|
|
18
20
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
21
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
|
|
@@ -135,6 +137,24 @@ try {
|
|
|
135
137
|
if (!launchHeader.includes("█████") || !launchHeader.includes("v0.0.0") || launchHeader.split("\n").length < 9) {
|
|
136
138
|
throw new Error("large launch header did not render a centered multi-line title");
|
|
137
139
|
}
|
|
140
|
+
if (
|
|
141
|
+
formatElapsedDuration(0) !== "00:00" ||
|
|
142
|
+
formatElapsedDuration(65_000) !== "01:05" ||
|
|
143
|
+
formatElapsedDuration(3_665_000) !== "1:01:05"
|
|
144
|
+
) {
|
|
145
|
+
throw new Error("elapsed duration formatter returned an unexpected value");
|
|
146
|
+
}
|
|
147
|
+
const wrappedDockerCommand = dockerUserCommand("node --test 2>&1 | tail -30", {
|
|
148
|
+
category: "general-shell",
|
|
149
|
+
needsNetwork: false,
|
|
150
|
+
});
|
|
151
|
+
if (
|
|
152
|
+
dockerPolicyTimeoutMs({ needsNetwork: true }) !== 120000 ||
|
|
153
|
+
!wrappedDockerCommand.includes("timeout -k 5s 15s bash -lc") ||
|
|
154
|
+
!wrappedDockerCommand.includes("'node --test 2>&1 | tail -30'")
|
|
155
|
+
) {
|
|
156
|
+
throw new Error("docker sandbox command wrapper did not add a bounded inner timeout");
|
|
157
|
+
}
|
|
138
158
|
|
|
139
159
|
const promptLayout = buildPromptLayout(`${"x".repeat(180)}\nsecond line`, 95, 80, 24);
|
|
140
160
|
const promptText = promptLayout.renderedRows
|
|
@@ -260,7 +280,7 @@ try {
|
|
|
260
280
|
}
|
|
261
281
|
const helpResult = await runChat("/help\n/exit\n");
|
|
262
282
|
const misspelledAuxiliary = "/auxil" + "liary";
|
|
263
|
-
if (!helpResult.stdout.includes("/auxiliary") || helpResult.stdout.includes(misspelledAuxiliary)) {
|
|
283
|
+
if (!helpResult.stdout.includes("/auxiliary") || !helpResult.stdout.includes("/review") || helpResult.stdout.includes(misspelledAuxiliary)) {
|
|
264
284
|
throw new Error("interactive help did not expose only the correctly spelled /auxiliary command");
|
|
265
285
|
}
|
|
266
286
|
const zhHelpResult = await runCli(["chat", "--language", "zh-Hans"], "/help\n/exit\n");
|
|
@@ -271,6 +291,10 @@ try {
|
|
|
271
291
|
if (!skillsResult.stdout.includes("website-app") || !skillsResult.stdout.includes("Website And App Builder")) {
|
|
272
292
|
throw new Error("interactive /skills did not show matching built-in skills");
|
|
273
293
|
}
|
|
294
|
+
const reviewResult = await runChat("/review changed files only\n/exit\n");
|
|
295
|
+
if (!reviewResult.stdout.includes("Review focus: changed files only") || !reviewResult.stdout.includes("Mock run complete")) {
|
|
296
|
+
throw new Error("interactive /review did not launch the bounded review workflow");
|
|
297
|
+
}
|
|
274
298
|
const abbreviatedSkillsResult = await runChat("/sk website\n/ex\n");
|
|
275
299
|
if (abbreviatedSkillsResult.stdout.includes("Unknown command") || !abbreviatedSkillsResult.stdout.includes("website-app")) {
|
|
276
300
|
throw new Error("interactive slash command prefix did not auto-select the first matching command");
|
|
@@ -347,6 +371,7 @@ try {
|
|
|
347
371
|
"instructions-command",
|
|
348
372
|
"auxiliary-command-spelling",
|
|
349
373
|
"skills-command",
|
|
374
|
+
"review-command",
|
|
350
375
|
"slash-prefix-autoselect",
|
|
351
376
|
"slash-prefix-canonical-history",
|
|
352
377
|
"instructions-chat-edit",
|
|
@@ -20,5 +20,12 @@ tools:
|
|
|
20
20
|
|
|
21
21
|
Prioritize findings over summary. Inspect changed files, neighboring code, tests, and runtime assumptions. Report concrete risks with file paths, reproduction evidence, and suggested fixes.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Use a bounded review loop:
|
|
24
|
+
|
|
25
|
+
1. Start with git status/diff and project instructions or manifests.
|
|
26
|
+
2. Read high-signal files first: changed files, entry points, tests, package/build configs, and nearby code needed to prove a risk.
|
|
27
|
+
3. Avoid full-tree reads and generated/vendor/cache/binary folders such as `.git`, `node_modules`, `dist`, `build`, `target`, `coverage`, `.venv`, `__pycache__`, `.aginti-sessions`, `.sessions`, and artifacts.
|
|
28
|
+
4. Cap discovery at two passes unless a concrete finding needs one more neighboring file.
|
|
29
|
+
5. Run focused non-destructive checks when useful; do not install dependencies or run long broad suites for a review unless clearly justified.
|
|
24
30
|
|
|
31
|
+
If no findings are found, say that clearly and name residual risk or missing test coverage. Do not rewrite code during a review unless the user asks for fixes.
|
package/src/cli.js
CHANGED
|
@@ -607,6 +607,7 @@ const removeSessionAnsi = {
|
|
|
607
607
|
reset: "\x1b[0m",
|
|
608
608
|
bold: "\x1b[1m",
|
|
609
609
|
dim: "\x1b[2m",
|
|
610
|
+
red: "\x1b[31m",
|
|
610
611
|
inverse: "\x1b[7m",
|
|
611
612
|
};
|
|
612
613
|
|
|
@@ -624,10 +625,13 @@ function ellipsize(text, width) {
|
|
|
624
625
|
return `${value.slice(0, Math.max(0, width - 1))}…`;
|
|
625
626
|
}
|
|
626
627
|
|
|
627
|
-
function buttonLabel(label, focused, disabled = false) {
|
|
628
|
+
function buttonLabel(label, focused, disabled = false, danger = false) {
|
|
628
629
|
const text = ` ${label} `;
|
|
629
630
|
if (disabled) return removeSessionColor(text, removeSessionAnsi.dim);
|
|
630
|
-
|
|
631
|
+
const codes = [];
|
|
632
|
+
if (danger) codes.push(removeSessionAnsi.red, removeSessionAnsi.bold);
|
|
633
|
+
if (focused) codes.push(removeSessionAnsi.inverse, removeSessionAnsi.bold);
|
|
634
|
+
return codes.length > 0 ? removeSessionColor(text, ...codes) : text;
|
|
631
635
|
}
|
|
632
636
|
|
|
633
637
|
function renderSessionRemovalWizard(state) {
|
|
@@ -658,12 +662,12 @@ function renderSessionRemovalWizard(state) {
|
|
|
658
662
|
});
|
|
659
663
|
const footer =
|
|
660
664
|
state.phase === "confirm"
|
|
661
|
-
? `Confirm deletion: ${buttonLabel("
|
|
662
|
-
: `Actions: ${buttonLabel(`
|
|
665
|
+
? `Confirm deletion: ${buttonLabel("Delete", state.confirmFocus === "yes", false, true)} ${buttonLabel("Cancel", state.confirmFocus === "cancel")}`
|
|
666
|
+
: `Actions: ${buttonLabel(`Delete ${selectedCount}`, state.focus === "ok", selectedCount === 0, true)} ${buttonLabel("Cancel", state.focus === "cancel")}`;
|
|
663
667
|
const guidance =
|
|
664
668
|
state.phase === "confirm"
|
|
665
|
-
? "Left/Right switches choice. Enter confirms. Esc/q cancels."
|
|
666
|
-
: "Space toggles. Up/Down moves. Tab changes focus.
|
|
669
|
+
? "Left/Right switches choice. Enter/Space confirms. Esc/q cancels."
|
|
670
|
+
: "Space toggles or activates focused button. Up/Down moves. Tab changes focus. Esc/q cancels.";
|
|
667
671
|
const lines = [
|
|
668
672
|
`╭${border}╮`,
|
|
669
673
|
line(state.title),
|
|
@@ -733,7 +737,7 @@ async function promptRemoveSessions(candidates, { defaultSelectedIds = [], title
|
|
|
733
737
|
if (name === "escape" || name === "q") return cleanup(null);
|
|
734
738
|
if (state.phase === "confirm") {
|
|
735
739
|
if (name === "left" || name === "right" || name === "tab") state.confirmFocus = state.confirmFocus === "yes" ? "cancel" : "yes";
|
|
736
|
-
else if (name === "return" || name === "enter") return cleanup(state.confirmFocus === "yes" ? [...state.selected] : null);
|
|
740
|
+
else if (name === "return" || name === "enter" || name === "space") return cleanup(state.confirmFocus === "yes" ? [...state.selected] : null);
|
|
737
741
|
renderSessionRemovalWizard(state);
|
|
738
742
|
return;
|
|
739
743
|
}
|
|
@@ -741,7 +745,11 @@ async function promptRemoveSessions(candidates, { defaultSelectedIds = [], title
|
|
|
741
745
|
else if (name === "down") moveCursor(1);
|
|
742
746
|
else if (name === "pageup") moveCursor(-8);
|
|
743
747
|
else if (name === "pagedown") moveCursor(8);
|
|
744
|
-
else if (name === "space")
|
|
748
|
+
else if (name === "space") {
|
|
749
|
+
if (state.focus === "list") toggleCurrent();
|
|
750
|
+
else if (state.focus === "cancel") return cleanup(null);
|
|
751
|
+
else openConfirm();
|
|
752
|
+
}
|
|
745
753
|
else if (name === "tab") state.focus = state.focus === "list" ? "ok" : state.focus === "ok" ? "cancel" : "list";
|
|
746
754
|
else if (name === "left" || name === "right") state.focus = state.focus === "cancel" ? "ok" : "cancel";
|
|
747
755
|
else if (name === "return" || name === "enter") {
|
package/src/docker-sandbox.js
CHANGED
|
@@ -29,6 +29,28 @@ function shellEscape(value) {
|
|
|
29
29
|
return `'${String(value).replace(/'/g, `'\"'\"'`)}'`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function dockerPolicyTimeoutMs(policy = {}) {
|
|
33
|
+
if (policy.needsNetwork) return 120000;
|
|
34
|
+
if (policy.category === "toolchain") return 90000;
|
|
35
|
+
return 15000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function dockerExecTimeoutMs(policy = {}) {
|
|
39
|
+
return dockerPolicyTimeoutMs(policy) + 5000;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function dockerUserCommand(command, policy = {}) {
|
|
43
|
+
const seconds = Math.max(1, Math.ceil(dockerPolicyTimeoutMs(policy) / 1000));
|
|
44
|
+
const escapedCommand = shellEscape(String(command || ""));
|
|
45
|
+
return [
|
|
46
|
+
`if command -v timeout >/dev/null 2>&1; then`,
|
|
47
|
+
` timeout -k 5s ${seconds}s bash -lc ${escapedCommand}`,
|
|
48
|
+
`else`,
|
|
49
|
+
` bash -lc ${escapedCommand}`,
|
|
50
|
+
`fi`,
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
function buildDockerInvocation(args) {
|
|
33
55
|
return ["docker", ...args].map(shellEscape).join(" ");
|
|
34
56
|
}
|
|
@@ -232,7 +254,7 @@ function dockerCommand(command, policy) {
|
|
|
232
254
|
);
|
|
233
255
|
}
|
|
234
256
|
|
|
235
|
-
return [...envLines,
|
|
257
|
+
return [...envLines, dockerUserCommand(command, policy)].join("\n");
|
|
236
258
|
}
|
|
237
259
|
|
|
238
260
|
function dockerRunArgs(command, config, policy = evaluateCommandPolicy(command, config), persistentDirs = persistentDockerDirs(config)) {
|
|
@@ -289,7 +311,7 @@ function dockerRunArgs(command, config, policy = evaluateCommandPolicy(command,
|
|
|
289
311
|
export async function runDockerSandboxCommand(command, config, policy = evaluateCommandPolicy(command, config), options = {}) {
|
|
290
312
|
const persistentDirs = await ensurePersistentDockerDirs(config);
|
|
291
313
|
const result = await execDocker(dockerRunArgs(command, config, policy, persistentDirs), {
|
|
292
|
-
timeout: policy
|
|
314
|
+
timeout: dockerExecTimeoutMs(policy),
|
|
293
315
|
maxBuffer: 300 * 1024,
|
|
294
316
|
signal: options.signal,
|
|
295
317
|
});
|
|
@@ -366,7 +388,7 @@ export async function runDockerPreflight(config, options = {}) {
|
|
|
366
388
|
]) {
|
|
367
389
|
try {
|
|
368
390
|
const result = await execDocker(dockerRunArgs(command, config, { needsNetwork: false, category: "preflight" }, persistentDirs), {
|
|
369
|
-
timeout:
|
|
391
|
+
timeout: dockerExecTimeoutMs({ needsNetwork: false, category: "preflight" }),
|
|
370
392
|
maxBuffer: 100 * 1024,
|
|
371
393
|
});
|
|
372
394
|
results.push({ command, ok: true, stdout: result.stdout.trim(), stderr: result.stderr.trim() });
|
package/src/interactive-cli.js
CHANGED
|
@@ -69,6 +69,7 @@ const SLASH_COMMANDS = [
|
|
|
69
69
|
"/new",
|
|
70
70
|
"/resume",
|
|
71
71
|
"/sessions",
|
|
72
|
+
"/review",
|
|
72
73
|
"/rename",
|
|
73
74
|
"/skills",
|
|
74
75
|
"/skill",
|
|
@@ -235,6 +236,15 @@ function compactLine(value = "", limit = 96) {
|
|
|
235
236
|
return text.length <= limit ? text : `${text.slice(0, Math.max(limit - 1, 1))}…`;
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
export function formatElapsedDuration(ms = 0) {
|
|
240
|
+
const totalSeconds = Math.max(0, Math.floor((Number(ms) || 0) / 1000));
|
|
241
|
+
const seconds = totalSeconds % 60;
|
|
242
|
+
const minutes = Math.floor(totalSeconds / 60) % 60;
|
|
243
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
244
|
+
const two = (value) => String(value).padStart(2, "0");
|
|
245
|
+
return hours > 0 ? `${hours}:${two(minutes)}:${two(seconds)}` : `${two(minutes)}:${two(seconds)}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
238
248
|
function wrapTextLine(value = "", width = 72) {
|
|
239
249
|
const text = stripAnsi(String(value || ""));
|
|
240
250
|
if (text.length <= width) return [text];
|
|
@@ -635,6 +645,7 @@ function printHelp() {
|
|
|
635
645
|
` ${command("/auxiliary [status|grsai|venice|model [provider/model]|on|off|image]", "Manage optional auxiliary skills, including image generation.", "helpAuxiliary")}`,
|
|
636
646
|
` ${command("/new", "Start a fresh session on the next message.", "helpNew")}`,
|
|
637
647
|
` ${command("/resume <session-id>", "Continue a saved session.", "helpResume")}`,
|
|
648
|
+
` ${command("/review [focus]", "Run a bounded repo/diff review with controlled context gathering.", "helpReview")}`,
|
|
638
649
|
` ${command("/rename [title|auto]", "Rename the current session.", "helpRename")}`,
|
|
639
650
|
` ${command("/sessions", "List recent sessions in this project.", "helpSessions")}`,
|
|
640
651
|
` ${command("/skills [query]", "List Markdown skills selected for a topic.", "helpSkills")}`,
|
|
@@ -1252,6 +1263,8 @@ class LiveRunInput {
|
|
|
1252
1263
|
this.pendingAsap = [];
|
|
1253
1264
|
this.pendingQueued = [];
|
|
1254
1265
|
this.statusLine = "";
|
|
1266
|
+
this.statusStartedAt = 0;
|
|
1267
|
+
this.statusTimer = null;
|
|
1255
1268
|
this.wasRaw = Boolean(input.isRaw);
|
|
1256
1269
|
this.started = false;
|
|
1257
1270
|
this.handler = this.handleKey.bind(this);
|
|
@@ -1268,6 +1281,11 @@ class LiveRunInput {
|
|
|
1268
1281
|
input.setRawMode(true);
|
|
1269
1282
|
input.on("keypress", this.handler);
|
|
1270
1283
|
activeRunInput = this;
|
|
1284
|
+
this.statusStartedAt = Date.now();
|
|
1285
|
+
this.statusTimer = setInterval(() => {
|
|
1286
|
+
if (this.started && this.statusLine) this.renderNow();
|
|
1287
|
+
}, 1000);
|
|
1288
|
+
this.statusTimer.unref?.();
|
|
1271
1289
|
this.started = true;
|
|
1272
1290
|
this.renderNow();
|
|
1273
1291
|
return true;
|
|
@@ -1279,6 +1297,10 @@ class LiveRunInput {
|
|
|
1279
1297
|
clearImmediate(this.redrawHandle);
|
|
1280
1298
|
this.redrawHandle = null;
|
|
1281
1299
|
}
|
|
1300
|
+
if (this.statusTimer) {
|
|
1301
|
+
clearInterval(this.statusTimer);
|
|
1302
|
+
this.statusTimer = null;
|
|
1303
|
+
}
|
|
1282
1304
|
input.off("keypress", this.handler);
|
|
1283
1305
|
if (typeof input.setRawMode === "function") input.setRawMode(this.wasRaw);
|
|
1284
1306
|
this.clearForExternalOutput();
|
|
@@ -1310,6 +1332,12 @@ class LiveRunInput {
|
|
|
1310
1332
|
output.write(ansi.cursorShow);
|
|
1311
1333
|
}
|
|
1312
1334
|
|
|
1335
|
+
currentStatusLine() {
|
|
1336
|
+
if (!this.statusLine) return "";
|
|
1337
|
+
const elapsed = formatElapsedDuration(Date.now() - (this.statusStartedAt || Date.now()));
|
|
1338
|
+
return compactLine(`${elapsed} · ${this.statusLine}`, Math.max(terminalWidth() - 16, 36));
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1313
1341
|
renderNow() {
|
|
1314
1342
|
if (this.redrawHandle) {
|
|
1315
1343
|
clearImmediate(this.redrawHandle);
|
|
@@ -1318,7 +1346,7 @@ class LiveRunInput {
|
|
|
1318
1346
|
this.rendered = renderPromptBuffer(this.buffer, this.cursor, this.rendered, {
|
|
1319
1347
|
commandCwd: this.commandCwd,
|
|
1320
1348
|
language: this.state.language || cliLanguage,
|
|
1321
|
-
statusLine: this.
|
|
1349
|
+
statusLine: this.currentStatusLine(),
|
|
1322
1350
|
pendingAsap: this.pendingAsap,
|
|
1323
1351
|
pendingQueued: this.pendingQueued,
|
|
1324
1352
|
});
|
|
@@ -1350,7 +1378,7 @@ class LiveRunInput {
|
|
|
1350
1378
|
const layout = buildPromptLayout(this.buffer, this.cursor, terminalWidth(), terminalHeight(), {
|
|
1351
1379
|
commandCwd: this.commandCwd,
|
|
1352
1380
|
language: this.state.language || cliLanguage,
|
|
1353
|
-
statusLine: this.
|
|
1381
|
+
statusLine: this.currentStatusLine(),
|
|
1354
1382
|
pendingAsap: this.pendingAsap,
|
|
1355
1383
|
pendingQueued: this.pendingQueued,
|
|
1356
1384
|
});
|
|
@@ -2454,6 +2482,29 @@ async function promptAndSaveProviderKey(provider = "", state = null) {
|
|
|
2454
2482
|
applyAuthWizardResult(result, state);
|
|
2455
2483
|
}
|
|
2456
2484
|
|
|
2485
|
+
function buildReviewPrompt(focus = "") {
|
|
2486
|
+
const target = String(focus || "").trim();
|
|
2487
|
+
return [
|
|
2488
|
+
target ? `Review focus: ${target}` : "Review focus: current repository state, especially local changes if any.",
|
|
2489
|
+
"",
|
|
2490
|
+
"Run a bounded, evidence-based code review of this workspace. Default to read-only review; do not edit files unless the review focus explicitly asks for fixes.",
|
|
2491
|
+
"",
|
|
2492
|
+
"Review operating loop:",
|
|
2493
|
+
"1. Start with `git status --short`, `git diff --stat`, and project metadata. If git is unavailable, say so and continue from manifests.",
|
|
2494
|
+
"2. Read only the highest-signal context first: AGINTI.md/AGENTS.md/README, package/build manifests, entry points, tests, and files changed in git diff.",
|
|
2495
|
+
"3. Use `inspect_project`, `search_files`, and targeted `read_file`; avoid full-tree dumps. Prefer precise symbol/error searches over opening many files.",
|
|
2496
|
+
"4. Exclude generated, vendored, binary, cache, and large artifact paths: .git, node_modules, vendor, dist, build, out, target, coverage, .next, .turbo, .venv, __pycache__, .pytest_cache, .aginti-sessions, .sessions, artifacts, images/videos/PDFs unless directly relevant.",
|
|
2497
|
+
"5. Context budget: at most two discovery passes; at most 12 primary files read initially; expand only when a concrete risk requires neighboring code.",
|
|
2498
|
+
"6. Check likely validation commands from manifests, but run only focused non-destructive checks when useful. Bound shell checks with `timeout 30s ...` when available, avoid broad watch/dev commands, and do not install packages or run long broad suites unless clearly justified.",
|
|
2499
|
+
"7. Stop when you have enough evidence. Do not keep scanning just because more files exist.",
|
|
2500
|
+
"",
|
|
2501
|
+
"Final answer format:",
|
|
2502
|
+
"- Findings first, ordered by severity, with file/line references or exact evidence. Focus on bugs, regressions, security issues, data loss, broken UX/API behavior, and missing tests.",
|
|
2503
|
+
"- If no findings, state that clearly and list residual risks or unrun checks.",
|
|
2504
|
+
"- Then include a short `Files inspected` and `Checks run` section. Keep summary secondary and concise.",
|
|
2505
|
+
].join("\n");
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2457
2508
|
async function handleCommand(line, state, packageDir) {
|
|
2458
2509
|
const [rawCommand, ...rest] = line.slice(1).trim().split(/\s+/);
|
|
2459
2510
|
const command = resolveSlashCommand(rawCommand);
|
|
@@ -2605,6 +2656,19 @@ async function handleCommand(line, state, packageDir) {
|
|
|
2605
2656
|
}
|
|
2606
2657
|
return true;
|
|
2607
2658
|
}
|
|
2659
|
+
if (command === "review") {
|
|
2660
|
+
const previousProfile = state.taskProfile;
|
|
2661
|
+
const previousMaxSteps = state.maxSteps;
|
|
2662
|
+
try {
|
|
2663
|
+
state.taskProfile = "review";
|
|
2664
|
+
state.maxSteps = Math.max(state.maxSteps, 32);
|
|
2665
|
+
await runPrompt(buildReviewPrompt(value), state, packageDir);
|
|
2666
|
+
} finally {
|
|
2667
|
+
state.taskProfile = previousProfile;
|
|
2668
|
+
state.maxSteps = previousMaxSteps;
|
|
2669
|
+
}
|
|
2670
|
+
return true;
|
|
2671
|
+
}
|
|
2608
2672
|
if (command === "rename") {
|
|
2609
2673
|
if (!state.sessionId) {
|
|
2610
2674
|
printAgentMessage("No active session to rename. Start or resume a session first.");
|
package/src/task-profiles.js
CHANGED
|
@@ -20,6 +20,13 @@ export const TASK_PROFILES = {
|
|
|
20
20
|
"Bias toward senior large-repo engineering while still answering ordinary side questions. Inspect_project first unless context is already known, read AGINTI/AGENTS/README/manifests, locate entry points and tests, make a small explicit change plan, patch in coherent batches, run the narrowest relevant checks first, escalate to broader checks when stable, and summarize files changed, checks, tradeoffs, and remaining risks.",
|
|
21
21
|
tools: ["inspect_project", "search_files", "read_file", "apply_patch", "shell", "sandbox", "canvas"],
|
|
22
22
|
},
|
|
23
|
+
review: {
|
|
24
|
+
id: "review",
|
|
25
|
+
label: "Code review",
|
|
26
|
+
prompt:
|
|
27
|
+
"Bias toward bounded code review rather than implementation. Start with git status/diff and project instructions, then inspect manifests, entry points, tests, changed files, and only the neighboring code needed to prove or disprove concrete risks. Avoid full-tree scans, generated/vendor/cache/binary folders, and infinite context gathering. Do not edit files unless explicitly asked for fixes. Findings must come first, ordered by severity with file/line evidence; if no findings are found, say so and name residual risks and checks not run.",
|
|
28
|
+
tools: ["inspect_project", "search_files", "read_file", "shell", "web_search"],
|
|
29
|
+
},
|
|
23
30
|
writing: {
|
|
24
31
|
id: "writing",
|
|
25
32
|
label: "Book/script writing",
|
|
@@ -331,6 +338,12 @@ const PROFILE_ALIASES = {
|
|
|
331
338
|
etl: "data",
|
|
332
339
|
dataframe: "data",
|
|
333
340
|
qa: "qa",
|
|
341
|
+
review: "review",
|
|
342
|
+
reviews: "review",
|
|
343
|
+
"code-review": "review",
|
|
344
|
+
"code-audit": "review",
|
|
345
|
+
codereview: "review",
|
|
346
|
+
codeaudit: "review",
|
|
334
347
|
test: "qa",
|
|
335
348
|
testing: "qa",
|
|
336
349
|
ci: "qa",
|
|
@@ -412,6 +425,7 @@ export function getTaskProfile(value = "auto") {
|
|
|
412
425
|
export function defaultMaxStepsForProfile(value = "auto") {
|
|
413
426
|
const profile = normalizeTaskProfile(value);
|
|
414
427
|
if (profile === "code") return 36;
|
|
428
|
+
if (profile === "review") return 32;
|
|
415
429
|
if (profile === "large-codebase") return 36;
|
|
416
430
|
if (profile === "qa") return 40;
|
|
417
431
|
if (profile === "app") return 40;
|