@lazyingart/agintiflow 0.12.8 → 0.12.11
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 +4 -2
- package/docs/large-codebase-engineering.md +28 -2
- package/docs/productive-agent-roadmap.md +35 -0
- package/package.json +1 -1
- package/public/app.js +63 -10
- package/public/index.html +2 -2
- package/public/styles.css +58 -0
- package/scripts/smoke-capabilities.js +13 -1
- package/scripts/smoke-cli-chat.js +4 -1
- package/scripts/smoke-coding-tools.js +61 -1
- package/src/agent-runner.js +4 -0
- package/src/capabilities.js +6 -0
- package/src/cli.js +1 -1
- package/src/command-policy.js +32 -5
- package/src/config.js +9 -1
- package/src/engineering-guidance.js +7 -0
- package/src/interactive-cli.js +100 -12
- package/src/parallel-scouts.js +151 -6
- package/src/workspace-tools.js +13 -1
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ aginti
|
|
|
67
67
|
aginti chat
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed first, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. The input panel always shows the current `cwd` footer and a single live status row, so long goals and tool updates are compacted instead of flooding the transcript. Assistant responses start on a fresh line after the `aginti>` header with a colored response gutter and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, tables, and red/green patch diff lines. Resuming a session prints
|
|
70
|
+
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed first, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. The input panel always shows the current `cwd` footer and a single live status row, so long goals and tool updates are compacted instead of flooding the transcript. Assistant responses start on a fresh line after the `aginti>` header with a colored response gutter and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, tables, and red/green patch diff lines. Resuming a session prints the full saved chat history with wrapped messages before the prompt. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
71
71
|
|
|
72
72
|
`aginti init` creates `AGINTI.md` at the project root. This is the editable project-instruction file for both CLI and web runs, similar to `AGENTS.md` or project memory in other agents. Keep durable preferences, commands, and constraints there, but never secrets. You can edit it manually or ask in chat, for example: `update AGINTI.md to remember that this project uses pytest and npm run check`.
|
|
73
73
|
|
|
@@ -75,7 +75,9 @@ For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSee
|
|
|
75
75
|
|
|
76
76
|
For larger repositories, use `--profile large-codebase` or choose **Large codebase engineering** in the web UI. The web default stays **Auto**, and Auto now escalates codebase/system/debugging prompts to the same engineering loop when needed. Complex work routes to DeepSeek v4 pro, starts with `inspect_project`, then uses search/read/patch/check loops inspired by Codex, Copilot SDK, Claude Code, Gemini CLI, Qwen, and Claw Code. See [docs/large-codebase-engineering.md](docs/large-codebase-engineering.md).
|
|
77
77
|
|
|
78
|
-
AgInTiFlow can also spend cheap DeepSeek calls on parallel scout notes before the main executor starts a complicated task.
|
|
78
|
+
AgInTiFlow can also spend cheap DeepSeek calls on parallel scout notes before the main executor starts a complicated task. It first builds a bounded `inspect_project` context pack, then runs scouts for architecture, implementation, review, research, context mapping, tests, git workflow, integration, symbol tracing, and dependency risks. A coordinator Swarm Board is injected for the main agent. The executor still does the real file/shell/browser work itself. Disable with `--no-parallel-scouts` or set `--scout-count 1..10`.
|
|
79
|
+
|
|
80
|
+
The next productive-agent roadmap is tracked in [docs/productive-agent-roadmap.md](docs/productive-agent-roadmap.md): durable codebase maps, stronger scout blackboards, long-run checkpoints, LSP/symbol tools, test triage, and release automation.
|
|
79
81
|
|
|
80
82
|
For current docs, install errors, package/toolchain setup, and source discovery, the agent has a guarded `web_search` tool. It returns compact search results without browser search-engine loops and respects configured domain allowlists. Disable with `--no-web-search`.
|
|
81
83
|
|
|
@@ -13,6 +13,8 @@ Local agent references informed the design:
|
|
|
13
13
|
- Claw-style `doctor` discipline: check health and environment before treating system symptoms as code bugs.
|
|
14
14
|
- Claude/Codex-style context discipline: read project instructions, manifests, entry points, and failing tests before touching broad files.
|
|
15
15
|
|
|
16
|
+
The shared pattern is not “put the whole repo in context.” It is: build a cheap map, choose the next exact evidence, edit with deterministic patches, verify, then compact what changed. AgInTiFlow uses cheap DeepSeek calls to add more independent eyes around that loop, not to replace it.
|
|
17
|
+
|
|
16
18
|
## Skill vs Tool
|
|
17
19
|
|
|
18
20
|
The `large-codebase` profile is a skill: it changes the model’s engineering behavior. It tells DeepSeek v4 pro to orient first, plan minimally, patch incrementally, and verify.
|
|
@@ -38,6 +40,22 @@ For complicated tasks, the agent should follow this loop:
|
|
|
38
40
|
6. `run_command` for the narrowest relevant check first.
|
|
39
41
|
7. Broaden checks only after the focused check passes.
|
|
40
42
|
|
|
43
|
+
## Context Budgeting
|
|
44
|
+
|
|
45
|
+
Mature coding agents avoid keeping an entire growing project in the prompt. AgInTiFlow uses a layered context pack:
|
|
46
|
+
|
|
47
|
+
- Stable memory: `AGINTI.md`, `AGENTS.md`, README files, manifests, and package scripts.
|
|
48
|
+
- Project map: `inspect_project` summaries, language counts, source/test directories, and recommended reads.
|
|
49
|
+
- Active evidence: exact search hits, selected files, failing command output, and compact git status/diff.
|
|
50
|
+
- Patch context: only the nearby code needed for `apply_patch`, plus before/after hashes and compact diffs.
|
|
51
|
+
- Scout synthesis: cheap parallel DeepSeek scouts produce bounded advice, then a coordinator summary is injected instead of every long transcript becoming permanent context.
|
|
52
|
+
|
|
53
|
+
This keeps the main executor sober: it knows where it is in the repo, but it still re-reads exact files before editing and validates with commands rather than trusting stale memory.
|
|
54
|
+
|
|
55
|
+
## Git Discipline
|
|
56
|
+
|
|
57
|
+
When asked to commit, pull, merge, or push, the agent should run `git status --short` and `git diff --stat` first. It should commit only intended changes, use `git fetch` and `git pull --ff-only` when remote state matters, and stop for the user on conflicts, divergence, unrelated dirty files, or any merge/rebase/reset choice. Web and CLI logs fold long command output but keep full command summaries visible.
|
|
58
|
+
|
|
41
59
|
## CLI And Web Parity
|
|
42
60
|
|
|
43
61
|
Both CLI and web use the same task profile registry and the same model/tool schemas. Use either:
|
|
@@ -74,11 +92,19 @@ DeepSeek calls are cheap enough that complex tasks can use several short advisor
|
|
|
74
92
|
- Implementer: predicts patch boundaries and focused checks.
|
|
75
93
|
- Reviewer: looks for missing tests, risks, and instruction-compliance failures.
|
|
76
94
|
- Researcher: suggests `web_search` queries when current information may matter.
|
|
95
|
+
- Cartographer: builds a compact context map instead of dumping the whole tree.
|
|
96
|
+
- Tester: finds the narrowest useful checks and setup blockers.
|
|
97
|
+
- Git operator: keeps status/diff/commit/pull/push workflows disciplined.
|
|
98
|
+
- Integrator: looks for cross-stream conflicts and ordering constraints.
|
|
99
|
+
- Symbol tracer: predicts names, APIs, routes, schemas, and searches that connect the change.
|
|
100
|
+
- Dependency doctor: checks package managers, Docker/toolchain setup, generated artifacts, and install risks.
|
|
101
|
+
|
|
102
|
+
Before scouts run, AgInTiFlow builds a bounded shared context pack with `inspect_project`: manifests, top-level files, source/test directories, package scripts, language counts, git status hints, and recommended reads. This avoids the weak pattern of each scout rediscovering the repo differently or flooding the main context with an infinite tree.
|
|
77
103
|
|
|
78
|
-
Scout output is injected as advisory context only. The main agent still owns execution and must use real tools to inspect, edit, run commands, and finish. CLI flags:
|
|
104
|
+
Scout output is synthesized by a coordinator Swarm Board and injected as advisory context only. The board records shared context, execution order, conflicts/unknowns, must-read files/checks, and stop conditions. The main agent still owns execution and must use real tools to inspect, edit, run commands, and finish. CLI flags:
|
|
79
105
|
|
|
80
106
|
```bash
|
|
81
|
-
aginti --parallel-scouts --scout-count
|
|
107
|
+
aginti --parallel-scouts --scout-count 10 "fix this complicated repo bug"
|
|
82
108
|
aginti --no-parallel-scouts "run a cheap short task"
|
|
83
109
|
```
|
|
84
110
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Productive Agent Roadmap
|
|
2
|
+
|
|
3
|
+
AgInTiFlow should become a practical coding agent, not only a chat UI around a model. DeepSeek v4 pro is strong and cheap, so the advantage is to spend more inference on context, review, and verification while keeping the main executor focused.
|
|
4
|
+
|
|
5
|
+
## Lessons From Other Agents
|
|
6
|
+
|
|
7
|
+
- Codex: orient with file search, edit with deterministic patches, run focused checks, and keep git status visible.
|
|
8
|
+
- Claude Code: keep the terminal UX simple, project-local, resumable, and interruption-friendly.
|
|
9
|
+
- Gemini CLI: expose extensibility through tools, MCP-style capabilities, large-context workflows, and auth/setup discovery.
|
|
10
|
+
- Copilot SDK: make sessions, hooks, permissions, telemetry, and tool events first-class APIs.
|
|
11
|
+
- Claw Code: use doctor/preflight checks, container-first execution, parity tests, and explicit lifecycle state.
|
|
12
|
+
- LazyingArtBot: preserve workflow memory and artifacts so the agent can continue creative or production tasks later.
|
|
13
|
+
|
|
14
|
+
## Current Strengths
|
|
15
|
+
|
|
16
|
+
- Project-local CLI and web share sessions, preferences, artifacts, and `AGINTI.md`.
|
|
17
|
+
- DeepSeek flash/pro routing, Docker workspace mode, web search, image generation, canvas artifacts, and guarded file tools are already wired.
|
|
18
|
+
- `inspect_project`, `apply_patch`, command policy, git discipline, and the scout swarm give the model concrete tools instead of relying on memory.
|
|
19
|
+
|
|
20
|
+
## Missing Productive-Agent Pieces
|
|
21
|
+
|
|
22
|
+
1. Durable codebase map: cache `inspect_project`, symbol locations, test commands, package scripts, and recently changed files per project.
|
|
23
|
+
2. Scout blackboard: let scouts write short structured findings to a shared board, then run a coordinator pass that resolves conflicts before execution.
|
|
24
|
+
3. Long-run checkpoints: save phase state after inspect, patch, test, repair, and commit so a long task can recover after interruption.
|
|
25
|
+
4. Symbol/LSP tools: add find-definition, references, diagnostics, and document-symbols for JS/TS, Python, Rust, Go, and C/C++ where available.
|
|
26
|
+
5. Test triage: parse common test failures into file, symbol, command, and likely cause so repair loops stay narrow.
|
|
27
|
+
6. Patch review loop: before finalizing large edits, run a cheap reviewer scout over diffs, risks, and missing tests.
|
|
28
|
+
7. Dependency doctor: detect toolchain gaps and propose Docker/project-local setup before touching host installs.
|
|
29
|
+
8. Release assistant: status, diff, changelog, version bump, pack, publish, push, and rollback notes as a reusable workflow.
|
|
30
|
+
|
|
31
|
+
## Swarm Design
|
|
32
|
+
|
|
33
|
+
Scouts must not become noisy subagents. Each scout gets the same bounded context pack and one role. The coordinator produces a Swarm Board with shared context, execution order, disagreements, must-read files, checks, and stop conditions. The main agent still owns tool use and must re-read exact files before editing.
|
|
34
|
+
|
|
35
|
+
Use 3 scouts for medium tasks, 5 for large tasks, and up to 10 for complex multi-language or system tasks. More scouts are only useful when their roles cover different failure modes.
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -757,6 +757,21 @@ function setLogs(text, mode = "active") {
|
|
|
757
757
|
logsEl.textContent = text;
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
+
function outputLineCount(value = "") {
|
|
761
|
+
const text = String(value || "");
|
|
762
|
+
return text ? text.split(/\r?\n/).length : 0;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function outputPreviewText(value = "", maxLines = 18) {
|
|
766
|
+
const lines = String(value || "").split(/\r?\n/);
|
|
767
|
+
const shown = lines.slice(0, maxLines);
|
|
768
|
+
return {
|
|
769
|
+
text: shown.join("\n"),
|
|
770
|
+
hidden: Math.max(lines.length - shown.length, 0),
|
|
771
|
+
total: value ? lines.length : 0,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
760
775
|
function updateStopRunButton() {
|
|
761
776
|
if (!stopRunButton) return;
|
|
762
777
|
stopRunButton.hidden = currentRunStatus !== "running";
|
|
@@ -1027,7 +1042,7 @@ function formPayload() {
|
|
|
1027
1042
|
allowAuxiliaryTools: allowAuxiliaryToolsField?.checked ?? true,
|
|
1028
1043
|
allowWebSearch: allowWebSearchField?.checked ?? true,
|
|
1029
1044
|
allowParallelScouts: allowParallelScoutsField?.checked ?? true,
|
|
1030
|
-
parallelScoutCount: Number(parallelScoutCountField?.value) || 3,
|
|
1045
|
+
parallelScoutCount: Math.min(Math.max(Number(parallelScoutCountField?.value) || 3, 1), 10),
|
|
1031
1046
|
allowWrapperTools: allowWrapperToolsField.checked,
|
|
1032
1047
|
preferredWrapper: preferredWrapperField.value,
|
|
1033
1048
|
taskProfile: taskProfileField?.value || "auto",
|
|
@@ -1038,22 +1053,60 @@ function formPayload() {
|
|
|
1038
1053
|
};
|
|
1039
1054
|
}
|
|
1040
1055
|
|
|
1056
|
+
function renderCommandOutputLog(entry) {
|
|
1057
|
+
const data = entry.data || {};
|
|
1058
|
+
const command = data.command || "";
|
|
1059
|
+
const stdout = data.stdout || "";
|
|
1060
|
+
const stderr = data.stderr || "";
|
|
1061
|
+
const stdoutPreview = outputPreviewText(stdout);
|
|
1062
|
+
const stderrPreview = outputPreviewText(stderr);
|
|
1063
|
+
const totalLines = outputLineCount(stdout) + outputLineCount(stderr);
|
|
1064
|
+
const large = totalLines > 18 || stdout.length + stderr.length > 2600;
|
|
1065
|
+
const policy = data.commandPolicy?.category ? ` · ${data.commandPolicy.category}` : "";
|
|
1066
|
+
const status = data.blocked ? "blocked" : data.error ? "error" : "ok";
|
|
1067
|
+
const details = [
|
|
1068
|
+
stdout ? `<div class="log-stream-title">stdout</div><pre>${escapeHtml(stdoutPreview.text)}</pre>` : "",
|
|
1069
|
+
stdoutPreview.hidden > 0 ? `<div class="log-fold-note">... ${stdoutPreview.hidden} more stdout line(s) folded</div>` : "",
|
|
1070
|
+
stderr ? `<div class="log-stream-title">stderr</div><pre>${escapeHtml(stderrPreview.text)}</pre>` : "",
|
|
1071
|
+
stderrPreview.hidden > 0 ? `<div class="log-fold-note">... ${stderrPreview.hidden} more stderr line(s) folded</div>` : "",
|
|
1072
|
+
data.error ? `<div class="log-fold-note">${escapeHtml(data.error)}</div>` : "",
|
|
1073
|
+
]
|
|
1074
|
+
.filter(Boolean)
|
|
1075
|
+
.join("");
|
|
1076
|
+
|
|
1077
|
+
return `
|
|
1078
|
+
<details class="log-command" ${large ? "" : "open"}>
|
|
1079
|
+
<summary>
|
|
1080
|
+
<span>${escapeHtml(`[${entry.at}] command ${status}${policy}`)}</span>
|
|
1081
|
+
<code>${escapeHtml(command)}</code>
|
|
1082
|
+
<small>stdout=${outputLineCount(stdout)} stderr=${outputLineCount(stderr)}${large ? " · folded" : ""}</small>
|
|
1083
|
+
</summary>
|
|
1084
|
+
${details || `<div class="log-fold-note">No command output.</div>`}
|
|
1085
|
+
</details>
|
|
1086
|
+
`;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1041
1089
|
function renderLogs(run) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1090
|
+
logsEl.dataset.mode = "active";
|
|
1091
|
+
const parts = [
|
|
1092
|
+
`<div class="log-line">${escapeHtml(`status=${run.status} session=${run.sessionId} provider=${run.provider} model=${run.model}`)}</div>`,
|
|
1093
|
+
run.result ? `<div class="log-line">${escapeHtml(`result=${run.result}`)}</div>` : "",
|
|
1094
|
+
run.error ? `<div class="log-line error">${escapeHtml(`error=${run.error}`)}</div>` : "",
|
|
1095
|
+
];
|
|
1047
1096
|
|
|
1048
1097
|
for (const entry of run.logs || []) {
|
|
1049
|
-
|
|
1098
|
+
if (entry.message === "command.output") {
|
|
1099
|
+
parts.push(renderCommandOutputLog(entry));
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
parts.push(`<div class="log-line">${escapeHtml(`[${entry.at}] ${entry.kind}: ${entry.message}`)}</div>`);
|
|
1050
1103
|
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
1051
|
-
|
|
1104
|
+
parts.push(`<pre class="log-json">${escapeHtml(JSON.stringify(entry.data, null, 2))}</pre>`);
|
|
1052
1105
|
}
|
|
1053
|
-
lines.push("");
|
|
1054
1106
|
}
|
|
1055
1107
|
|
|
1056
|
-
|
|
1108
|
+
logsEl.innerHTML = parts.filter(Boolean).join("");
|
|
1109
|
+
logsEl.scrollTop = logsEl.scrollHeight;
|
|
1057
1110
|
}
|
|
1058
1111
|
|
|
1059
1112
|
function escapeHtml(value) {
|
package/public/index.html
CHANGED
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
|
|
206
206
|
<label>
|
|
207
207
|
<span data-i18n="parallelScoutCountLabel">Scout count</span>
|
|
208
|
-
<input id="parallelScoutCount" name="parallelScoutCount" type="number" min="1" max="
|
|
208
|
+
<input id="parallelScoutCount" name="parallelScoutCount" type="number" min="1" max="10" value="3" />
|
|
209
209
|
</label>
|
|
210
210
|
|
|
211
211
|
<div class="grid wrapper-controls">
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
<div id="run-meta" class="subtle"></div>
|
|
339
339
|
</div>
|
|
340
340
|
</div>
|
|
341
|
-
<
|
|
341
|
+
<div id="logs" class="logs" data-i18n="noRunStarted">No run started.</div>
|
|
342
342
|
</section>
|
|
343
343
|
</section>
|
|
344
344
|
</main>
|
package/public/styles.css
CHANGED
|
@@ -530,6 +530,64 @@ button.danger {
|
|
|
530
530
|
overflow-wrap: anywhere;
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
+
.log-line {
|
|
534
|
+
margin: 0 0 10px;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.log-line.error {
|
|
538
|
+
color: #fecaca;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.log-json,
|
|
542
|
+
.log-command pre {
|
|
543
|
+
margin: 8px 0 12px;
|
|
544
|
+
padding: 10px;
|
|
545
|
+
border-radius: 10px;
|
|
546
|
+
background: rgba(15, 23, 42, 0.78);
|
|
547
|
+
color: #e5e7eb;
|
|
548
|
+
border: 1px solid rgba(148, 163, 184, 0.18);
|
|
549
|
+
white-space: pre-wrap;
|
|
550
|
+
overflow: auto;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.log-command {
|
|
554
|
+
margin: 0 0 10px;
|
|
555
|
+
border: 1px solid rgba(148, 163, 184, 0.18);
|
|
556
|
+
border-radius: 12px;
|
|
557
|
+
background: rgba(255, 255, 255, 0.04);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.log-command summary {
|
|
561
|
+
display: grid;
|
|
562
|
+
grid-template-columns: minmax(0, 1fr);
|
|
563
|
+
gap: 4px;
|
|
564
|
+
cursor: pointer;
|
|
565
|
+
padding: 10px 12px;
|
|
566
|
+
color: #dbeafe;
|
|
567
|
+
white-space: normal;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.log-command summary code {
|
|
571
|
+
color: #fef3c7;
|
|
572
|
+
overflow-wrap: anywhere;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.log-command summary small,
|
|
576
|
+
.log-stream-title,
|
|
577
|
+
.log-fold-note {
|
|
578
|
+
color: #94a3b8;
|
|
579
|
+
font-size: 0.78rem;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.log-command[open] {
|
|
583
|
+
background: rgba(255, 255, 255, 0.06);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.log-stream-title,
|
|
587
|
+
.log-fold-note {
|
|
588
|
+
padding: 0 12px 8px;
|
|
589
|
+
}
|
|
590
|
+
|
|
533
591
|
.chat-panel {
|
|
534
592
|
display: grid;
|
|
535
593
|
gap: 12px;
|
|
@@ -53,6 +53,18 @@ try {
|
|
|
53
53
|
capabilities.checks.some((check) => check.name === "bash-syntax-policy" && check.ok),
|
|
54
54
|
"bash -n maintenance script policy is not allowed"
|
|
55
55
|
);
|
|
56
|
+
assert(
|
|
57
|
+
capabilities.checks.some((check) => check.name === "git-status-policy" && check.ok),
|
|
58
|
+
"git status policy is not allowed"
|
|
59
|
+
);
|
|
60
|
+
assert(
|
|
61
|
+
capabilities.checks.some((check) => check.name === "git-commit-policy" && check.ok),
|
|
62
|
+
"git commit policy is not allowed"
|
|
63
|
+
);
|
|
64
|
+
assert(
|
|
65
|
+
capabilities.checks.some((check) => check.name === "git-pull-ff-only-policy" && check.ok),
|
|
66
|
+
"unsafe git pull policy was not blocked"
|
|
67
|
+
);
|
|
56
68
|
assert(
|
|
57
69
|
capabilities.maintenancePolicy.some((check) => check.command.startsWith("sudo") && !check.allowed),
|
|
58
70
|
"sudo maintenance command was not blocked"
|
|
@@ -79,7 +91,7 @@ try {
|
|
|
79
91
|
{
|
|
80
92
|
ok: true,
|
|
81
93
|
projectRoot: tempRoot,
|
|
82
|
-
checks: ["aginti-md-init", "capabilities-cli", "doctor-capabilities", "maintenance-policy", "trusted-docker-policy"],
|
|
94
|
+
checks: ["aginti-md-init", "capabilities-cli", "doctor-capabilities", "maintenance-policy", "trusted-docker-policy", "git-policy"],
|
|
83
95
|
},
|
|
84
96
|
null,
|
|
85
97
|
2
|
|
@@ -150,13 +150,16 @@ try {
|
|
|
150
150
|
if (!latest.stdout.includes("resume history") || !latest.stdout.includes("Mock run complete")) {
|
|
151
151
|
throw new Error("bare aginti resume did not preview saved chat history");
|
|
152
152
|
}
|
|
153
|
+
if (latest.stdout.includes("showing=") || latest.stdout.includes("…")) {
|
|
154
|
+
throw new Error("resume history should render full saved messages instead of compact previews");
|
|
155
|
+
}
|
|
153
156
|
|
|
154
157
|
console.log(
|
|
155
158
|
JSON.stringify(
|
|
156
159
|
{
|
|
157
160
|
ok: true,
|
|
158
161
|
projectRoot: tempRoot,
|
|
159
|
-
checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-
|
|
162
|
+
checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-full"],
|
|
160
163
|
},
|
|
161
164
|
null,
|
|
162
165
|
2
|
|
@@ -7,7 +7,7 @@ import { repairModelMessageHistory, runAgent } from "../src/agent-runner.js";
|
|
|
7
7
|
import { resolveRuntimeConfig } from "../src/config.js";
|
|
8
8
|
import { engineeringGuidanceForTask, recommendedMaxStepsForTask } from "../src/engineering-guidance.js";
|
|
9
9
|
import { selectModelRoute } from "../src/model-routing.js";
|
|
10
|
-
import { shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
10
|
+
import { listParallelScouts, runParallelScouts, shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
11
11
|
import { SessionStore } from "../src/session-store.js";
|
|
12
12
|
import { searchWeb } from "../src/web-search.js";
|
|
13
13
|
import { executeWorkspaceTool } from "../src/workspace-tools.js";
|
|
@@ -127,6 +127,16 @@ try {
|
|
|
127
127
|
),
|
|
128
128
|
"parallel scouts did not enable for complex auto task"
|
|
129
129
|
);
|
|
130
|
+
const scoutNames = listParallelScouts().map((scout) => scout.name);
|
|
131
|
+
assert(scoutNames.length >= 10, "parallel scout roster did not expose 10 scout roles");
|
|
132
|
+
for (const expectedScout of ["cartographer", "git-operator", "integrator", "symbol-tracer", "dependency-doctor"]) {
|
|
133
|
+
assert(scoutNames.includes(expectedScout), `parallel scout roster missing ${expectedScout}`);
|
|
134
|
+
}
|
|
135
|
+
const scoutConfig = resolveRuntimeConfig(
|
|
136
|
+
{ provider: "mock", parallelScoutCount: 99, commandCwd: workspace },
|
|
137
|
+
{ baseDir: runtimeDir, packageDir: repoRoot, provider: "mock", commandCwd: workspace }
|
|
138
|
+
);
|
|
139
|
+
assert(scoutConfig.parallelScoutCount === 10, "parallel scout count did not clamp to 10");
|
|
130
140
|
const drySearch = await searchWeb(
|
|
131
141
|
{ query: "AgInTiFlow web_search smoke", maxResults: 2 },
|
|
132
142
|
{ allowWebSearch: true, webSearchDryRun: true }
|
|
@@ -166,6 +176,53 @@ try {
|
|
|
166
176
|
assert(inspected.sourceDirs.some((item) => item.path === "src"), "inspect_project did not identify src directory");
|
|
167
177
|
assert(inspected.testFiles.some((item) => item.path === "test/index.test.js"), "inspect_project did not identify test file");
|
|
168
178
|
assert(inspected.recommendedReads.includes("package.json"), "inspect_project did not recommend package.json");
|
|
179
|
+
const fakeScoutPrompts = [];
|
|
180
|
+
const fakeScoutClient = {
|
|
181
|
+
chat: {
|
|
182
|
+
completions: {
|
|
183
|
+
create: async ({ messages }) => {
|
|
184
|
+
const systemContent = messages.find((message) => message.role === "system")?.content || "";
|
|
185
|
+
const userContent = messages.find((message) => message.role === "user")?.content || "";
|
|
186
|
+
fakeScoutPrompts.push(userContent);
|
|
187
|
+
const role = userContent.match(/Scout role: ([^\n]+)/)?.[1] || "coordinator";
|
|
188
|
+
return {
|
|
189
|
+
choices: [
|
|
190
|
+
{
|
|
191
|
+
message: {
|
|
192
|
+
content: systemContent.includes("synthesize")
|
|
193
|
+
? "Swarm Board: read package.json first, inspect src/test, patch narrowly, run npm test, stop on unrelated git changes."
|
|
194
|
+
: `Advice from ${role}: use the shared context pack and inspect package.json before editing.`,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
const scoutRun = await runParallelScouts(
|
|
204
|
+
fakeScoutClient,
|
|
205
|
+
{
|
|
206
|
+
provider: "deepseek",
|
|
207
|
+
model: "deepseek-v4-flash",
|
|
208
|
+
commandCwd: workspace,
|
|
209
|
+
allowFileTools: true,
|
|
210
|
+
parallelScoutCount: 10,
|
|
211
|
+
goal: "fix this complicated repo test bug",
|
|
212
|
+
taskProfile: "large-codebase",
|
|
213
|
+
sandboxMode: "host",
|
|
214
|
+
packageInstallPolicy: "block",
|
|
215
|
+
},
|
|
216
|
+
{ goal: "fix this complicated repo test bug", meta: {}, plan: "Inspect and patch." }
|
|
217
|
+
);
|
|
218
|
+
assert(scoutRun.requested === 10 && scoutRun.completed === 10, "parallel scout fake run did not complete 10 scouts");
|
|
219
|
+
assert(scoutRun.contextPack.includes("package.json"), "parallel scout context pack did not include manifest evidence");
|
|
220
|
+
assert(scoutRun.summary.includes("## shared context pack"), "parallel scout summary omitted shared context pack");
|
|
221
|
+
assert(scoutRun.summary.includes("## coordinator"), "parallel scout summary omitted coordinator synthesis");
|
|
222
|
+
assert(
|
|
223
|
+
fakeScoutPrompts.filter((prompt) => /Shared context pack:[\s\S]*package\.json/.test(prompt)).length >= 10,
|
|
224
|
+
"parallel scouts did not receive the shared context pack"
|
|
225
|
+
);
|
|
169
226
|
|
|
170
227
|
const inspectRun = await runMock("Inspect this large codebase and recommend next reads.", "coding-inspect");
|
|
171
228
|
assert(
|
|
@@ -306,8 +363,11 @@ try {
|
|
|
306
363
|
"auto_system_pro_route",
|
|
307
364
|
"auto_engineering_guidance",
|
|
308
365
|
"parallel_scout_trigger",
|
|
366
|
+
"parallel_scout_roster",
|
|
367
|
+
"parallel_scout_count_clamp",
|
|
309
368
|
"web_search_dry_run",
|
|
310
369
|
"inspect_project",
|
|
370
|
+
"parallel_scout_context_pack",
|
|
311
371
|
"mock_inspect_project",
|
|
312
372
|
"write_file",
|
|
313
373
|
"duplicate_write_failed",
|
package/src/agent-runner.js
CHANGED
|
@@ -1175,6 +1175,8 @@ export async function runAgent(config) {
|
|
|
1175
1175
|
model: scouts.model,
|
|
1176
1176
|
requested: scouts.requested,
|
|
1177
1177
|
completed: scouts.completed,
|
|
1178
|
+
contextPack: scouts.contextPack ? scouts.contextPack.slice(0, 1200) : "",
|
|
1179
|
+
synthesis: scouts.synthesis || "",
|
|
1178
1180
|
};
|
|
1179
1181
|
state.messages.push({
|
|
1180
1182
|
role: "user",
|
|
@@ -1184,6 +1186,8 @@ export async function runAgent(config) {
|
|
|
1184
1186
|
model: scouts.model,
|
|
1185
1187
|
requested: scouts.requested,
|
|
1186
1188
|
completed: scouts.completed,
|
|
1189
|
+
contextPack: scouts.contextPack || "",
|
|
1190
|
+
synthesis: scouts.synthesis || "",
|
|
1187
1191
|
scouts: scouts.scouts.map((scout) => ({
|
|
1188
1192
|
name: scout.name,
|
|
1189
1193
|
model: scout.model,
|
package/src/capabilities.js
CHANGED
|
@@ -129,6 +129,9 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
129
129
|
const nodeTestPolicy = evaluateCommandPolicy("node --test round9-node-app/test/app.test.js", config);
|
|
130
130
|
const bashSyntaxPolicy = evaluateCommandPolicy("bash -n maintenance/setup-conda.sh", config);
|
|
131
131
|
const texPolicy = evaluateCommandPolicy("pdflatex -interaction=nonstopmode -halt-on-error docs/note.tex", config);
|
|
132
|
+
const gitStatusPolicy = evaluateCommandPolicy("git status --short", config);
|
|
133
|
+
const gitCommitPolicy = evaluateCommandPolicy('git commit -m "test commit"', config);
|
|
134
|
+
const gitPullPolicy = evaluateCommandPolicy("git pull", config);
|
|
132
135
|
|
|
133
136
|
const checks = [
|
|
134
137
|
capability("node", node.available, node),
|
|
@@ -156,6 +159,9 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
156
159
|
capability("node-test-policy", Boolean(nodeTestPolicy.allowed), nodeTestPolicy),
|
|
157
160
|
capability("bash-syntax-policy", Boolean(bashSyntaxPolicy.allowed), bashSyntaxPolicy),
|
|
158
161
|
capability("tex-policy", Boolean(texPolicy.allowed), texPolicy),
|
|
162
|
+
capability("git-status-policy", Boolean(gitStatusPolicy.allowed), gitStatusPolicy),
|
|
163
|
+
capability("git-commit-policy", Boolean(gitCommitPolicy.allowed), gitCommitPolicy),
|
|
164
|
+
capability("git-pull-ff-only-policy", !gitPullPolicy.allowed, gitPullPolicy),
|
|
159
165
|
];
|
|
160
166
|
|
|
161
167
|
return {
|
package/src/cli.js
CHANGED
|
@@ -261,7 +261,7 @@ export function parseArgs(argv) {
|
|
|
261
261
|
|
|
262
262
|
function printUsage() {
|
|
263
263
|
console.log(
|
|
264
|
-
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti login deepseek|openai|grsai OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count
|
|
264
|
+
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti login deepseek|openai|grsai OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
|
|
265
265
|
);
|
|
266
266
|
}
|
|
267
267
|
|
package/src/command-policy.js
CHANGED
|
@@ -46,6 +46,19 @@ const NETWORK_FETCH_PATTERNS = [
|
|
|
46
46
|
/^wget\s+(?:-[A-Za-z0-9]*\s+)*(?:-O\s+[-\w./]+\s+)?https?:\/\/\S+$/,
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
+
const GIT_WORKFLOW_PATTERNS = [
|
|
50
|
+
/^git\s+add(?:\s+[-\w./*]+)+$/,
|
|
51
|
+
/^git\s+commit\s+(?:-a\s+)?-m\s+(['"])[^'"\n]{1,220}\1$/,
|
|
52
|
+
/^git\s+fetch(?:\s+[-\w./:=]+)*$/,
|
|
53
|
+
/^git\s+pull\s+--ff-only(?:\s+[-\w./:=]+)*$/,
|
|
54
|
+
/^git\s+push(?:\s+[-\w./:=]+)*$/,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const UNSAFE_GIT_PATTERNS = [
|
|
58
|
+
/^git\s+pull\b(?!\s+--ff-only(?:\s|$))/,
|
|
59
|
+
/^git\s+(merge|rebase|reset|checkout|switch|clean)\b/,
|
|
60
|
+
];
|
|
61
|
+
|
|
49
62
|
const TOOLCHAIN_PATTERNS = [
|
|
50
63
|
/^python(?:3)?\s+[-\w./]+\.py(?:\s+[-\w./:=]+)*$/,
|
|
51
64
|
/^latexmk\s+(?=[-\w./=\s]*-pdf\b)(?:(?:-cd|-pdf|-interaction=nonstopmode|-halt-on-error|-output-directory=[-\w./]+)\s+)+[-\w./]+\.tex$/,
|
|
@@ -90,10 +103,6 @@ const BLOCKED_WRITE_TOKENS = [
|
|
|
90
103
|
" touch",
|
|
91
104
|
" tee",
|
|
92
105
|
"-delete",
|
|
93
|
-
"git add",
|
|
94
|
-
"git commit",
|
|
95
|
-
"git push",
|
|
96
|
-
"git pull",
|
|
97
106
|
"git checkout",
|
|
98
107
|
"git switch",
|
|
99
108
|
"git reset",
|
|
@@ -153,6 +162,14 @@ function classifySimpleCommand(normalized) {
|
|
|
153
162
|
if (SENSITIVE_COMMAND_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
154
163
|
return { category: "blocked", reason: "Command is blocked because it references secrets or credential files." };
|
|
155
164
|
}
|
|
165
|
+
if (matchAny(UNSAFE_GIT_PATTERNS, normalized)) {
|
|
166
|
+
return {
|
|
167
|
+
category: "destructive",
|
|
168
|
+
needsApproval: true,
|
|
169
|
+
reason:
|
|
170
|
+
"Git merge/rebase/reset/checkout/switch/clean, and non-ff-only pulls, can rewrite or conflict with local work. Inspect status/diff first and ask the user when the repository is divergent or conflicted.",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
156
173
|
|
|
157
174
|
if (matchAny(SAFE_WORKSPACE_WRITE_PATTERNS, normalized)) {
|
|
158
175
|
const target = normalized.replace(/^mkdir\s+-p\s+/, "");
|
|
@@ -170,6 +187,16 @@ function classifySimpleCommand(normalized) {
|
|
|
170
187
|
reason: `Command changes workspace file mode: ${normalized}`,
|
|
171
188
|
};
|
|
172
189
|
}
|
|
190
|
+
if (matchAny(GIT_WORKFLOW_PATTERNS, normalized)) {
|
|
191
|
+
const remote = /^git\s+(fetch|pull|push)\b/.test(normalized);
|
|
192
|
+
const writesWorkspace = /^git\s+(add|commit|pull)\b/.test(normalized);
|
|
193
|
+
return {
|
|
194
|
+
category: remote ? "git-remote" : "git-workflow",
|
|
195
|
+
needsNetwork: remote,
|
|
196
|
+
writesWorkspace,
|
|
197
|
+
reason: "Git workflow command. Agent should run git status/diff first and stop on conflicts or divergence.",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
173
200
|
|
|
174
201
|
const lowered = ` ${normalized.toLowerCase()} `;
|
|
175
202
|
if (BLOCKED_WRITE_TOKENS.some((part) => lowered.includes(part))) {
|
|
@@ -341,7 +368,7 @@ export function evaluateCommandPolicy(command, config) {
|
|
|
341
368
|
};
|
|
342
369
|
}
|
|
343
370
|
|
|
344
|
-
if (classification.
|
|
371
|
+
if (classification.needsNetwork && config.useDockerSandbox && !trustedDockerShell) {
|
|
345
372
|
return {
|
|
346
373
|
allowed: false,
|
|
347
374
|
...classification,
|
package/src/config.js
CHANGED
|
@@ -17,6 +17,10 @@ function parseNumber(value, fallback) {
|
|
|
17
17
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function clampNumber(value, min, max) {
|
|
21
|
+
return Math.min(Math.max(value, min), max);
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
function parseList(value) {
|
|
21
25
|
if (!value) return [];
|
|
22
26
|
return String(value)
|
|
@@ -95,7 +99,11 @@ export function resolveRuntimeConfig(args, overrides = {}) {
|
|
|
95
99
|
overrides.allowParallelScouts ?? args.allowParallelScouts ?? process.env.AGINTI_PARALLEL_SCOUTS,
|
|
96
100
|
true
|
|
97
101
|
),
|
|
98
|
-
parallelScoutCount:
|
|
102
|
+
parallelScoutCount: clampNumber(
|
|
103
|
+
parseNumber(overrides.parallelScoutCount ?? args.parallelScoutCount ?? process.env.AGINTI_SCOUT_COUNT, 3),
|
|
104
|
+
1,
|
|
105
|
+
10
|
|
106
|
+
),
|
|
99
107
|
preferredWrapper: normalizeWrapperName(
|
|
100
108
|
overrides.preferredWrapper ?? args.preferredWrapper ?? process.env.PREFERRED_WRAPPER ?? process.env.AGENT_WRAPPER
|
|
101
109
|
),
|
|
@@ -43,6 +43,12 @@ const LANGUAGE_HINTS = [
|
|
|
43
43
|
text:
|
|
44
44
|
"System/shell: diagnose first with read-only commands, capture versions/logs, make reversible scripts, use Docker for installs/toolchains, and only use host-level changes when policy explicitly allows them.",
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
id: "git",
|
|
48
|
+
pattern: /\b(git|commit|push|pull|merge|rebase|branch|remote|status|diff)\b/i,
|
|
49
|
+
text:
|
|
50
|
+
"Git: always run git status --short and git diff --stat before commit/push. Commit only requested changes, use a clear message, run git fetch before push when remote state matters, prefer git pull --ff-only, and stop to ask if there are conflicts, unrelated dirty files, divergent branches, or ambiguous merge choices.",
|
|
51
|
+
},
|
|
46
52
|
{
|
|
47
53
|
id: "r-stats",
|
|
48
54
|
pattern: /\b(rstats|r language|cmdstanr|stan|renv|tidyverse|shiny)\b/i,
|
|
@@ -77,6 +83,7 @@ export function engineeringGuidanceForTask(goal = "", taskProfile = "auto") {
|
|
|
77
83
|
"Use the proven coding-agent loop: inspect_project, read instructions/manifests, search exact symbols/errors, patch small coherent batches, run focused checks, repair failures, then summarize changed files and residual risks.",
|
|
78
84
|
"Keep CLI and web behavior equivalent: use the same workspace, sessions, profiles, file tools, shell policy, Docker mounts, and canvas artifacts.",
|
|
79
85
|
"For large repositories, preserve context by reading fewer but more relevant files; prefer deterministic tools and diffs over long model memory.",
|
|
86
|
+
"Build a compact context pack before major edits: project instructions, manifests/scripts, git status/diff, relevant symbols/search hits, target files, and the narrowest checks. Do not paste whole trees or huge files into model context.",
|
|
80
87
|
"For system repair, act like a doctor: gather evidence first, avoid silent destructive host changes, prefer Docker or project-local scripts for installs, and make every stronger action explicit in logs.",
|
|
81
88
|
];
|
|
82
89
|
|
package/src/interactive-cli.js
CHANGED
|
@@ -150,6 +150,21 @@ function compactLine(value = "", limit = 96) {
|
|
|
150
150
|
return text.length <= limit ? text : `${text.slice(0, Math.max(limit - 1, 1))}…`;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
function wrapTextLine(value = "", width = 72) {
|
|
154
|
+
const text = stripAnsi(String(value || ""));
|
|
155
|
+
if (text.length <= width) return [text];
|
|
156
|
+
const chunks = [];
|
|
157
|
+
let remaining = text;
|
|
158
|
+
while (remaining.length > width) {
|
|
159
|
+
let splitAt = remaining.lastIndexOf(" ", width);
|
|
160
|
+
if (splitAt < Math.floor(width * 0.45)) splitAt = width;
|
|
161
|
+
chunks.push(remaining.slice(0, splitAt).trimEnd());
|
|
162
|
+
remaining = remaining.slice(splitAt).trimStart();
|
|
163
|
+
}
|
|
164
|
+
if (remaining) chunks.push(remaining);
|
|
165
|
+
return chunks;
|
|
166
|
+
}
|
|
167
|
+
|
|
153
168
|
export function stripMarkdown(text) {
|
|
154
169
|
const lines = String(text || "").split(/\r?\n/);
|
|
155
170
|
let inFence = false;
|
|
@@ -320,6 +335,36 @@ function printAgentMessage(text) {
|
|
|
320
335
|
for (const line of lines) outputLine(`${responsePrefix()}${line}`);
|
|
321
336
|
}
|
|
322
337
|
|
|
338
|
+
function printPreviewBlock(role, text, { time = "", bg = ansi.systemBg, maxLines = 5 } = {}) {
|
|
339
|
+
const header = [label(role, bg).trimEnd(), time ? color(time, ansi.dim) : ""].filter(Boolean).join(" ");
|
|
340
|
+
outputLine(header);
|
|
341
|
+
const width = Math.max(terminalWidth() - 8, 38);
|
|
342
|
+
const rendered = stripMarkdown(text)
|
|
343
|
+
.split(/\r?\n/)
|
|
344
|
+
.map((line) => line.trimEnd())
|
|
345
|
+
.filter((line, index, all) => line.trim() || (index > 0 && index < all.length - 1));
|
|
346
|
+
const wrapped = rendered.flatMap((line) => wrapTextLine(line || " ", width)).slice(0, maxLines);
|
|
347
|
+
const truncated = rendered.flatMap((line) => wrapTextLine(line || " ", width)).length > maxLines;
|
|
348
|
+
for (const [index, line] of (wrapped.length ? wrapped : ["(empty)"]).entries()) {
|
|
349
|
+
const suffix = truncated && index === wrapped.length - 1 ? " …" : "";
|
|
350
|
+
outputLine(`${color(" | ", bg)} ${line}${suffix}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function printHistoryBlock(role, text, { time = "", bg = ansi.systemBg } = {}) {
|
|
355
|
+
const header = [label(role, bg).trimEnd(), time ? color(time, ansi.dim) : ""].filter(Boolean).join(" ");
|
|
356
|
+
outputLine(header);
|
|
357
|
+
const width = Math.max(terminalWidth() - 8, 38);
|
|
358
|
+
const rendered = stripMarkdown(text)
|
|
359
|
+
.split(/\r?\n/)
|
|
360
|
+
.map((line) => line.trimEnd());
|
|
361
|
+
const lines = rendered.length ? rendered : ["(empty)"];
|
|
362
|
+
for (const line of lines) {
|
|
363
|
+
const wrapped = wrapTextLine(line || " ", width);
|
|
364
|
+
for (const wrappedLine of wrapped) outputLine(`${color(" | ", bg)} ${wrappedLine}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
323
368
|
function printSystemLine(text) {
|
|
324
369
|
if (!String(text || "").trim()) {
|
|
325
370
|
outputLine("");
|
|
@@ -328,6 +373,50 @@ function printSystemLine(text) {
|
|
|
328
373
|
outputLine(`${label("state", ansi.systemBg)} ${color(text, ansi.dim)}`);
|
|
329
374
|
}
|
|
330
375
|
|
|
376
|
+
function outputStats(value = "") {
|
|
377
|
+
const text = String(value || "");
|
|
378
|
+
return {
|
|
379
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
380
|
+
lines: text ? text.split(/\r?\n/).length : 0,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function outputPreview(value = "", maxLines = 12) {
|
|
385
|
+
const lines = String(value || "")
|
|
386
|
+
.split(/\r?\n/)
|
|
387
|
+
.filter((line, index, all) => line.trim() || index < all.length - 1);
|
|
388
|
+
const shown = lines.slice(0, maxLines);
|
|
389
|
+
const hidden = Math.max(lines.length - shown.length, 0);
|
|
390
|
+
return { shown, hidden, total: lines.length };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function printCommandOutputLog(data = {}) {
|
|
394
|
+
const command = String(data.command || "").trim();
|
|
395
|
+
const stdout = String(data.stdout || "");
|
|
396
|
+
const stderr = String(data.stderr || "");
|
|
397
|
+
const isGit = /^git\b/.test(command);
|
|
398
|
+
const failed = Boolean(data.error || stderr || data.blocked);
|
|
399
|
+
if (!isGit && !failed) return;
|
|
400
|
+
|
|
401
|
+
const stdoutStats = outputStats(stdout);
|
|
402
|
+
const stderrStats = outputStats(stderr);
|
|
403
|
+
const policy = data.commandPolicy?.category ? ` ${data.commandPolicy.category}` : "";
|
|
404
|
+
outputLine(
|
|
405
|
+
`${label("shell", ansi.systemBg)} ${compactLine(command || "(command)", 86)}${policy} stdout=${stdoutStats.lines} stderr=${stderrStats.lines}`
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
for (const [name, value] of [
|
|
409
|
+
["stdout", stdout],
|
|
410
|
+
["stderr", stderr],
|
|
411
|
+
]) {
|
|
412
|
+
if (!value) continue;
|
|
413
|
+
const preview = outputPreview(value, 12);
|
|
414
|
+
outputLine(`${color(" | ", ansi.systemBg)} ${name}`);
|
|
415
|
+
for (const line of preview.shown) outputLine(`${color(" | ", ansi.systemBg)} ${line}`);
|
|
416
|
+
if (preview.hidden > 0) outputLine(`${color(" | ", ansi.systemBg)} ... ${preview.hidden} more line(s) folded`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
331
420
|
function printHeading(text) {
|
|
332
421
|
outputLine(color(stripMarkdown(text), ansi.bold, ansi.cyan));
|
|
333
422
|
}
|
|
@@ -393,7 +482,7 @@ function printHelp() {
|
|
|
393
482
|
" /sessions List recent sessions in this project.",
|
|
394
483
|
" /profile <name> Set task profile, e.g. code, website, latex, maintenance.",
|
|
395
484
|
" /web-search on|off Enable or disable the web_search tool.",
|
|
396
|
-
" /scouts on|off|<1-
|
|
485
|
+
" /scouts on|off|<1-10> Enable parallel DeepSeek scouts and set scout count.",
|
|
397
486
|
" /routing <mode> Set routing: smart, fast, complex, manual.",
|
|
398
487
|
" /provider <name> Set provider: deepseek, openai, mock.",
|
|
399
488
|
" /model <name> Set an explicit model, or /model auto.",
|
|
@@ -1223,20 +1312,14 @@ async function latestSession() {
|
|
|
1223
1312
|
return sessions[0] || null;
|
|
1224
1313
|
}
|
|
1225
1314
|
|
|
1226
|
-
function compactHistoryText(content = "", limit = 170) {
|
|
1227
|
-
const rendered = stripAnsi(stripMarkdown(String(content || "")));
|
|
1228
|
-
return compactLine(rendered.replace(/\s+/g, " "), limit);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
1315
|
function printHistoryEntry(entry) {
|
|
1232
1316
|
const role = entry.role === "assistant" ? "aginti" : entry.role === "user" ? "user" : String(entry.role || "note");
|
|
1233
1317
|
const bg = role === "aginti" ? ansi.agentBg : role === "user" ? ansi.userBg : ansi.systemBg;
|
|
1234
1318
|
const time = entry.at ? new Date(entry.at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
|
|
1235
|
-
|
|
1236
|
-
outputLine(`${label(role, bg)} ${color("|", bg)} ${compactHistoryText(entry.content)}${suffix}`);
|
|
1319
|
+
printHistoryBlock(role, entry.content, { time, bg });
|
|
1237
1320
|
}
|
|
1238
1321
|
|
|
1239
|
-
async function printResumeHistory(state, { limit =
|
|
1322
|
+
async function printResumeHistory(state, { limit = 0 } = {}) {
|
|
1240
1323
|
if (!state.sessionId) return;
|
|
1241
1324
|
const store = new SessionStore(projectPaths(process.cwd()).sessionsDir, state.sessionId);
|
|
1242
1325
|
const saved = await store.loadState().catch(() => null);
|
|
@@ -1246,8 +1329,10 @@ async function printResumeHistory(state, { limit = 8 } = {}) {
|
|
|
1246
1329
|
return;
|
|
1247
1330
|
}
|
|
1248
1331
|
|
|
1249
|
-
const shown = chat.slice(-limit);
|
|
1250
|
-
printSystemLine(
|
|
1332
|
+
const shown = limit > 0 ? chat.slice(-limit) : chat;
|
|
1333
|
+
printSystemLine(
|
|
1334
|
+
`resume history session=${state.sessionId} messages=${chat.length}${limit > 0 ? ` showing=${shown.length}/${chat.length}` : ""}`
|
|
1335
|
+
);
|
|
1251
1336
|
for (const entry of shown) printHistoryEntry(entry);
|
|
1252
1337
|
}
|
|
1253
1338
|
|
|
@@ -1521,7 +1606,7 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1521
1606
|
} else {
|
|
1522
1607
|
state.allowParallelScouts = true;
|
|
1523
1608
|
const count = Number(value);
|
|
1524
|
-
if (Number.isFinite(count) && count > 0) state.parallelScoutCount = Math.min(Math.max(count, 1),
|
|
1609
|
+
if (Number.isFinite(count) && count > 0) state.parallelScoutCount = Math.min(Math.max(count, 1), 10);
|
|
1525
1610
|
}
|
|
1526
1611
|
printSystemLine(`parallelScouts=${state.allowParallelScouts ? "on" : "off"} count=${state.parallelScoutCount}`);
|
|
1527
1612
|
return true;
|
|
@@ -1673,6 +1758,9 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
1673
1758
|
printSystemLine(text);
|
|
1674
1759
|
}
|
|
1675
1760
|
},
|
|
1761
|
+
onLog: (message, data = {}) => {
|
|
1762
|
+
if (message === "command.output") printCommandOutputLog(data);
|
|
1763
|
+
},
|
|
1676
1764
|
onEvent: (type, data = {}) => {
|
|
1677
1765
|
if (type === "plan.created") {
|
|
1678
1766
|
printStatusEvent(state, "planned");
|
package/src/parallel-scouts.js
CHANGED
|
@@ -2,6 +2,10 @@ import { engineeringGuidanceForTask } from "./engineering-guidance.js";
|
|
|
2
2
|
import { getModelPresets } from "./model-routing.js";
|
|
3
3
|
import { getTaskProfile, normalizeTaskProfile } from "./task-profiles.js";
|
|
4
4
|
import { redactSensitiveText } from "./redaction.js";
|
|
5
|
+
import { executeWorkspaceTool } from "./workspace-tools.js";
|
|
6
|
+
|
|
7
|
+
const MAX_CONTEXT_PACK_CHARS = 3800;
|
|
8
|
+
const MAX_SCOUT_CONTENT_CHARS = 1200;
|
|
5
9
|
|
|
6
10
|
const SCOUTS = [
|
|
7
11
|
{
|
|
@@ -24,8 +28,42 @@ const SCOUTS = [
|
|
|
24
28
|
prompt:
|
|
25
29
|
"If current external information may matter, suggest exact web_search queries and source types. Otherwise say no web search needed.",
|
|
26
30
|
},
|
|
31
|
+
{
|
|
32
|
+
name: "cartographer",
|
|
33
|
+
prompt:
|
|
34
|
+
"Design a compact context map: key instructions, manifests, entry points, tests, symbols, and files the executor should read. Avoid dumping a full tree.",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "tester",
|
|
38
|
+
prompt:
|
|
39
|
+
"Identify the narrowest checks to run first, then broader checks. Include likely setup blockers and how to validate without wasting steps.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "git-operator",
|
|
43
|
+
prompt:
|
|
44
|
+
"If git is relevant, outline the safe git sequence: status/diff first, commit boundaries, fetch/pull --ff-only, push, and stop conditions.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "integrator",
|
|
48
|
+
prompt:
|
|
49
|
+
"Look across workstreams for conflicts, shared files, ordering constraints, and what each scout might be missing. Keep it execution-focused.",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "symbol-tracer",
|
|
53
|
+
prompt:
|
|
54
|
+
"Infer likely symbols, APIs, routes, commands, schemas, or config keys that connect the change. Suggest exact searches before editing.",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "dependency-doctor",
|
|
58
|
+
prompt:
|
|
59
|
+
"Identify dependency, environment, package-manager, Docker, and generated-artifact risks. Prefer project-local or sandboxed setup and focused verification.",
|
|
60
|
+
},
|
|
27
61
|
];
|
|
28
62
|
|
|
63
|
+
export function listParallelScouts() {
|
|
64
|
+
return SCOUTS.map((scout) => ({ name: scout.name, prompt: scout.prompt }));
|
|
65
|
+
}
|
|
66
|
+
|
|
29
67
|
function shouldUseComplexScouts(config, state) {
|
|
30
68
|
const profile = normalizeTaskProfile(config.taskProfile);
|
|
31
69
|
const goal = String(config.goal || state.goal || "");
|
|
@@ -50,20 +88,88 @@ function scoutModel(config) {
|
|
|
50
88
|
return config.model;
|
|
51
89
|
}
|
|
52
90
|
|
|
53
|
-
function
|
|
91
|
+
function truncateText(text, limit = MAX_CONTEXT_PACK_CHARS) {
|
|
92
|
+
const value = String(text || "");
|
|
93
|
+
if (value.length <= limit) return value;
|
|
94
|
+
return `${value.slice(0, limit - 80)}\n... [truncated ${value.length - limit + 80} chars]`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function listValues(items, limit, mapper) {
|
|
98
|
+
const values = Array.isArray(items) ? items.slice(0, limit).map(mapper).filter(Boolean) : [];
|
|
99
|
+
if (!values.length) return "";
|
|
100
|
+
const suffix = Array.isArray(items) && items.length > limit ? `, ... +${items.length - limit}` : "";
|
|
101
|
+
return `${values.join(", ")}${suffix}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function packageScriptValues(scripts) {
|
|
105
|
+
if (!Array.isArray(scripts) || !scripts.length) return "";
|
|
106
|
+
return listValues(scripts, 14, (item) => `${item.name}: ${item.command}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function buildScoutContextPack(config) {
|
|
110
|
+
if (!config.allowFileTools) {
|
|
111
|
+
return "Context pack: workspace file tools are disabled. Scouts should recommend the minimal inspection commands/files instead of assuming repository structure.";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const inspected = await executeWorkspaceTool(
|
|
116
|
+
"inspect_project",
|
|
117
|
+
{ path: ".", maxDepth: 4, limit: 1000, includeFiles: false },
|
|
118
|
+
{
|
|
119
|
+
...config,
|
|
120
|
+
allowFileTools: true,
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
if (!inspected?.ok) {
|
|
124
|
+
return `Context pack: inspect_project unavailable (${inspected?.reason || inspected?.error || "unknown error"}).`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const packageScripts = packageScriptValues(inspected.packageScripts);
|
|
128
|
+
const sections = [
|
|
129
|
+
`Context pack for ${config.commandCwd}`,
|
|
130
|
+
`Summary: ${inspected.summary || "no summary"}`,
|
|
131
|
+
inspected.counts
|
|
132
|
+
? `Counts: files=${inspected.counts.files} dirs=${inspected.counts.directories} bytes=${inspected.counts.totalBytes}`
|
|
133
|
+
: "",
|
|
134
|
+
`Top level: ${listValues(inspected.topLevel, 20, (item) => `${item.type}:${item.path}`)}`,
|
|
135
|
+
`Manifests: ${listValues(inspected.manifestFiles, 18, (item) => item.path)}`,
|
|
136
|
+
`Source dirs: ${listValues(inspected.sourceDirs, 14, (item) => item.path)}`,
|
|
137
|
+
`Tests: ${listValues(inspected.testFiles, 18, (item) => item.path)}`,
|
|
138
|
+
`Package managers: ${listValues(inspected.packageManagers, 8, (item) => item.name || item.path || item)}`,
|
|
139
|
+
packageScripts ? `Package scripts: ${packageScripts}` : "",
|
|
140
|
+
`Languages: ${listValues(inspected.languageCounts, 10, (item) => `${item.name}:${item.count}`)}`,
|
|
141
|
+
inspected.git?.present
|
|
142
|
+
? `Git: present; start with ${inspected.git.recommendedCommands?.join(" && ") || "git status --short"}; ${inspected.git.workflow}`
|
|
143
|
+
: "Git: not detected at workspace root.",
|
|
144
|
+
`Recommended reads: ${listValues(inspected.recommendedReads, 16, (item) => item)}`,
|
|
145
|
+
`Engineering hints: ${listValues(inspected.engineeringHints, 8, (item) => item)}`,
|
|
146
|
+
].filter(Boolean);
|
|
147
|
+
|
|
148
|
+
return truncateText(redactSensitiveText(sections.join("\n")));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return `Context pack: inspect_project failed (${redactSensitiveText(error instanceof Error ? error.message : String(error))}).`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function capScoutContent(content) {
|
|
155
|
+
return truncateText(redactSensitiveText(content || "").trim(), MAX_SCOUT_CONTENT_CHARS);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function scoutMessages(config, state, scout, contextPack) {
|
|
54
159
|
const profile = getTaskProfile(config.taskProfile);
|
|
55
160
|
const guidance = engineeringGuidanceForTask(config.goal || state.goal || "", config.taskProfile);
|
|
56
161
|
return [
|
|
57
162
|
{
|
|
58
163
|
role: "system",
|
|
59
164
|
content:
|
|
60
|
-
"You are a parallel scout for AgInTiFlow. Your answer is advisory only: do not claim work is done, do not ask questions, do not use tools, and keep under 180 words.",
|
|
165
|
+
"You are a parallel scout for AgInTiFlow. Your answer is advisory only: do not claim work is done, do not ask questions, do not use tools, and keep under 180 words. Work from the shared context pack and state dependencies on other scout roles when relevant.",
|
|
61
166
|
},
|
|
62
167
|
{
|
|
63
168
|
role: "user",
|
|
64
169
|
content: [
|
|
65
170
|
`Scout role: ${scout.name}`,
|
|
66
171
|
scout.prompt,
|
|
172
|
+
`Shared context pack:\n${contextPack}`,
|
|
67
173
|
`Goal: ${config.goal || state.goal || ""}`,
|
|
68
174
|
`Task profile: ${profile.label}. ${profile.prompt}`,
|
|
69
175
|
guidance,
|
|
@@ -80,24 +186,51 @@ function scoutMessages(config, state, scout) {
|
|
|
80
186
|
];
|
|
81
187
|
}
|
|
82
188
|
|
|
189
|
+
async function synthesizeScouts(client, config, model, scouts, contextPack) {
|
|
190
|
+
const usable = scouts.filter((scout) => scout.content);
|
|
191
|
+
if (usable.length < 2) return "";
|
|
192
|
+
const response = await client.chat.completions.create(
|
|
193
|
+
{
|
|
194
|
+
model,
|
|
195
|
+
temperature: 0,
|
|
196
|
+
messages: [
|
|
197
|
+
{
|
|
198
|
+
role: "system",
|
|
199
|
+
content:
|
|
200
|
+
"You synthesize parallel coding-agent scout notes into a Swarm Board. Produce under 320 words with: shared context, execution order, conflicts/unknowns, must-read files/checks, and stop conditions. Prefer concrete paths/commands from the context pack. Do not claim work is done.",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
role: "user",
|
|
204
|
+
content: [`## shared context pack\n${contextPack}`, ...usable.map((scout) => `## ${scout.name}\n${scout.content}`)].join(
|
|
205
|
+
"\n\n"
|
|
206
|
+
),
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
config.abortSignal ? { signal: config.abortSignal } : undefined
|
|
211
|
+
);
|
|
212
|
+
return redactSensitiveText(response.choices[0]?.message?.content || "").trim();
|
|
213
|
+
}
|
|
214
|
+
|
|
83
215
|
export async function runParallelScouts(client, config, state) {
|
|
84
216
|
const count = Math.min(Math.max(Number(config.parallelScoutCount) || 3, 1), SCOUTS.length);
|
|
85
217
|
const selected = SCOUTS.slice(0, count);
|
|
86
218
|
const model = scoutModel(config);
|
|
219
|
+
const contextPack = await buildScoutContextPack(config);
|
|
87
220
|
const settled = await Promise.allSettled(
|
|
88
221
|
selected.map(async (scout) => {
|
|
89
222
|
const response = await client.chat.completions.create(
|
|
90
223
|
{
|
|
91
224
|
model,
|
|
92
225
|
temperature: 0,
|
|
93
|
-
messages: scoutMessages(config, state, scout),
|
|
226
|
+
messages: scoutMessages(config, state, scout, contextPack),
|
|
94
227
|
},
|
|
95
228
|
config.abortSignal ? { signal: config.abortSignal } : undefined
|
|
96
229
|
);
|
|
97
230
|
return {
|
|
98
231
|
name: scout.name,
|
|
99
232
|
model,
|
|
100
|
-
content:
|
|
233
|
+
content: capScoutContent(response.choices[0]?.message?.content || ""),
|
|
101
234
|
};
|
|
102
235
|
})
|
|
103
236
|
);
|
|
@@ -111,12 +244,22 @@ export async function runParallelScouts(client, config, state) {
|
|
|
111
244
|
};
|
|
112
245
|
});
|
|
113
246
|
const completed = scouts.filter((scout) => scout.content).length;
|
|
247
|
+
const synthesis =
|
|
248
|
+
completed > 1
|
|
249
|
+
? await synthesizeScouts(client, config, model, scouts, contextPack).catch((error) =>
|
|
250
|
+
`Synthesis failed: ${redactSensitiveText(error instanceof Error ? error.message : String(error))}`
|
|
251
|
+
)
|
|
252
|
+
: "";
|
|
114
253
|
const summary = [
|
|
115
|
-
"Parallel scout notes. Treat these as advisory, not completed work.",
|
|
254
|
+
"Parallel scout swarm notes. Treat these as advisory shared context, not completed work.",
|
|
255
|
+
`\n## shared context pack\n${contextPack}`,
|
|
256
|
+
synthesis ? `\n## coordinator\n${synthesis}` : "",
|
|
116
257
|
...scouts.map((scout) =>
|
|
117
258
|
scout.content ? `\n## ${scout.name}\n${scout.content}` : `\n## ${scout.name}\nScout failed: ${scout.error || "unknown error"}`
|
|
118
259
|
),
|
|
119
|
-
]
|
|
260
|
+
]
|
|
261
|
+
.filter(Boolean)
|
|
262
|
+
.join("\n");
|
|
120
263
|
|
|
121
264
|
return {
|
|
122
265
|
ok: completed > 0,
|
|
@@ -124,6 +267,8 @@ export async function runParallelScouts(client, config, state) {
|
|
|
124
267
|
requested: selected.length,
|
|
125
268
|
completed,
|
|
126
269
|
scouts,
|
|
270
|
+
contextPack,
|
|
271
|
+
synthesis,
|
|
127
272
|
summary,
|
|
128
273
|
};
|
|
129
274
|
}
|
package/src/workspace-tools.js
CHANGED
|
@@ -509,6 +509,10 @@ async function inspectProject(config, args) {
|
|
|
509
509
|
manifestFiles.some((item) => item.path.endsWith("Cargo.toml")) ? "cargo" : "",
|
|
510
510
|
manifestFiles.some((item) => item.path.endsWith("go.mod")) ? "go" : "",
|
|
511
511
|
].filter(Boolean);
|
|
512
|
+
const gitPresent = await fs
|
|
513
|
+
.stat(path.join(target.absolutePath, ".git"))
|
|
514
|
+
.then(() => true)
|
|
515
|
+
.catch(() => false);
|
|
512
516
|
|
|
513
517
|
const summary = {
|
|
514
518
|
ok: true,
|
|
@@ -532,13 +536,21 @@ async function inspectProject(config, args) {
|
|
|
532
536
|
languageCounts: sortCounts(languageCounts),
|
|
533
537
|
packageManagers,
|
|
534
538
|
packageScripts,
|
|
539
|
+
git: {
|
|
540
|
+
present: gitPresent,
|
|
541
|
+
recommendedCommands: gitPresent ? ["git status --short", "git diff --stat"] : [],
|
|
542
|
+
workflow: gitPresent
|
|
543
|
+
? "Before commit/push: inspect git status and diff, avoid unrelated files, prefer git pull --ff-only, and stop on conflicts or divergence."
|
|
544
|
+
: "",
|
|
545
|
+
},
|
|
535
546
|
recommendedReads: [],
|
|
536
547
|
engineeringHints: [
|
|
537
548
|
"Read AGINTI/AGENTS/README/manifests before editing.",
|
|
538
549
|
"Use search_files to locate symbols and tests, then read exact files.",
|
|
539
550
|
"Use apply_patch for source edits and run the smallest relevant check first.",
|
|
540
551
|
"If a change spans modules, patch in small batches and verify after each batch.",
|
|
541
|
-
|
|
552
|
+
gitPresent ? "For git tasks, run status/diff first; commit only intended changes and stop on conflicts/divergence." : "",
|
|
553
|
+
].filter(Boolean),
|
|
542
554
|
};
|
|
543
555
|
summary.recommendedReads = recommendedReads(summary);
|
|
544
556
|
if (includeFiles) summary.files = files.slice(0, limit);
|