@lazyingart/agintiflow 0.12.11 → 0.13.0
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 +1 -1
- package/docs/large-codebase-engineering.md +2 -2
- package/docs/productive-agent-roadmap.md +3 -3
- package/package.json +1 -1
- package/scripts/smoke-coding-tools.js +12 -0
- package/src/agent-runner.js +9 -0
- package/src/capabilities.js +10 -1
- package/src/codebase-map.js +151 -0
- package/src/parallel-scouts.js +71 -49
- package/src/project.js +3 -0
- package/src/session-store.js +9 -0
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ 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. It first
|
|
78
|
+
AgInTiFlow can also spend cheap DeepSeek calls on parallel scout notes before the main executor starts a complicated task. It first writes a bounded project map to `.aginti/codebase-map.json`, 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 and saved as `artifacts/scout-blackboard.json` in the session. The executor still does the real file/shell/browser work itself. Disable with `--no-parallel-scouts` or set `--scout-count 1..10`.
|
|
79
79
|
|
|
80
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.
|
|
81
81
|
|
|
@@ -99,9 +99,9 @@ DeepSeek calls are cheap enough that complex tasks can use several short advisor
|
|
|
99
99
|
- Symbol tracer: predicts names, APIs, routes, schemas, and searches that connect the change.
|
|
100
100
|
- Dependency doctor: checks package managers, Docker/toolchain setup, generated artifacts, and install risks.
|
|
101
101
|
|
|
102
|
-
Before scouts run, AgInTiFlow builds a bounded shared context pack
|
|
102
|
+
Before scouts run, AgInTiFlow builds a bounded shared context pack from a durable project map at `.aginti/codebase-map.json`: 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.
|
|
103
103
|
|
|
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:
|
|
104
|
+
Scout output is synthesized by a coordinator Swarm Board, persisted as `artifacts/scout-blackboard.json`, 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:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
107
|
aginti --parallel-scouts --scout-count 10 "fix this complicated repo bug"
|
|
@@ -19,8 +19,8 @@ AgInTiFlow should become a practical coding agent, not only a chat UI around a m
|
|
|
19
19
|
|
|
20
20
|
## Missing Productive-Agent Pieces
|
|
21
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.
|
|
22
|
+
1. Durable codebase map: cache `inspect_project`, symbol locations, test commands, package scripts, and recently changed files per project. Initial slice implemented as `.aginti/codebase-map.json` with manifests, source/test dirs, package scripts, languages, git hints, and recommended reads.
|
|
23
|
+
2. Scout blackboard: let scouts write short structured findings to a shared board, then run a coordinator pass that resolves conflicts before execution. Initial slice implemented as per-session `artifacts/scout-blackboard.json` with role lanes, findings, coordinator handoff, and codebase-map metadata.
|
|
24
24
|
3. Long-run checkpoints: save phase state after inspect, patch, test, repair, and commit so a long task can recover after interruption.
|
|
25
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
26
|
5. Test triage: parse common test failures into file, symbol, command, and likely cause so repair loops stay narrow.
|
|
@@ -30,6 +30,6 @@ AgInTiFlow should become a practical coding agent, not only a chat UI around a m
|
|
|
30
30
|
|
|
31
31
|
## Swarm Design
|
|
32
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.
|
|
33
|
+
Scouts must not become noisy subagents. Each scout gets the same bounded context pack generated from the durable codebase map 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
34
|
|
|
35
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
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { repairModelMessageHistory, runAgent } from "../src/agent-runner.js";
|
|
7
7
|
import { resolveRuntimeConfig } from "../src/config.js";
|
|
8
|
+
import { readCodebaseMap } from "../src/codebase-map.js";
|
|
8
9
|
import { engineeringGuidanceForTask, recommendedMaxStepsForTask } from "../src/engineering-guidance.js";
|
|
9
10
|
import { selectModelRoute } from "../src/model-routing.js";
|
|
10
11
|
import { listParallelScouts, runParallelScouts, shouldRunParallelScouts } from "../src/parallel-scouts.js";
|
|
@@ -219,10 +220,19 @@ try {
|
|
|
219
220
|
assert(scoutRun.contextPack.includes("package.json"), "parallel scout context pack did not include manifest evidence");
|
|
220
221
|
assert(scoutRun.summary.includes("## shared context pack"), "parallel scout summary omitted shared context pack");
|
|
221
222
|
assert(scoutRun.summary.includes("## coordinator"), "parallel scout summary omitted coordinator synthesis");
|
|
223
|
+
assert(scoutRun.codebaseMap?.fingerprint, "parallel scout run did not return durable codebase map metadata");
|
|
224
|
+
assert(scoutRun.blackboard?.lanes?.length === 10, "parallel scout blackboard did not include all scout lanes");
|
|
225
|
+
assert(scoutRun.blackboard?.coordinator.includes("Swarm Board"), "parallel scout blackboard did not retain coordinator synthesis");
|
|
222
226
|
assert(
|
|
223
227
|
fakeScoutPrompts.filter((prompt) => /Shared context pack:[\s\S]*package\.json/.test(prompt)).length >= 10,
|
|
224
228
|
"parallel scouts did not receive the shared context pack"
|
|
225
229
|
);
|
|
230
|
+
const codebaseMap = await readCodebaseMap(workspace);
|
|
231
|
+
assert(codebaseMap.ok && codebaseMap.map.fingerprint === scoutRun.codebaseMap.fingerprint, "durable codebase map was not persisted");
|
|
232
|
+
const blackboardStore = new SessionStore(runtimeDir, "blackboard-smoke");
|
|
233
|
+
const blackboardPath = await blackboardStore.saveJsonArtifact("scout-blackboard.json", scoutRun.blackboard);
|
|
234
|
+
const blackboardJson = JSON.parse(await fs.readFile(blackboardPath, "utf8"));
|
|
235
|
+
assert(blackboardJson.lanes.length === 10, "scout blackboard artifact did not persist lanes");
|
|
226
236
|
|
|
227
237
|
const inspectRun = await runMock("Inspect this large codebase and recommend next reads.", "coding-inspect");
|
|
228
238
|
assert(
|
|
@@ -368,6 +378,8 @@ try {
|
|
|
368
378
|
"web_search_dry_run",
|
|
369
379
|
"inspect_project",
|
|
370
380
|
"parallel_scout_context_pack",
|
|
381
|
+
"durable_codebase_map",
|
|
382
|
+
"scout_blackboard",
|
|
371
383
|
"mock_inspect_project",
|
|
372
384
|
"write_file",
|
|
373
385
|
"duplicate_write_failed",
|
package/src/agent-runner.js
CHANGED
|
@@ -1170,11 +1170,16 @@ export async function runAgent(config) {
|
|
|
1170
1170
|
|
|
1171
1171
|
if (shouldRunParallelScouts(config, state)) {
|
|
1172
1172
|
const scouts = await runParallelScouts(client, config, state);
|
|
1173
|
+
const blackboardPath = scouts.blackboard
|
|
1174
|
+
? await store.saveJsonArtifact("scout-blackboard.json", scouts.blackboard).catch(() => "")
|
|
1175
|
+
: "";
|
|
1173
1176
|
state.meta.parallelScoutsCompleted = true;
|
|
1174
1177
|
state.meta.parallelScouts = {
|
|
1175
1178
|
model: scouts.model,
|
|
1176
1179
|
requested: scouts.requested,
|
|
1177
1180
|
completed: scouts.completed,
|
|
1181
|
+
codebaseMap: scouts.codebaseMap || null,
|
|
1182
|
+
blackboardPath,
|
|
1178
1183
|
contextPack: scouts.contextPack ? scouts.contextPack.slice(0, 1200) : "",
|
|
1179
1184
|
synthesis: scouts.synthesis || "",
|
|
1180
1185
|
};
|
|
@@ -1186,8 +1191,11 @@ export async function runAgent(config) {
|
|
|
1186
1191
|
model: scouts.model,
|
|
1187
1192
|
requested: scouts.requested,
|
|
1188
1193
|
completed: scouts.completed,
|
|
1194
|
+
codebaseMap: scouts.codebaseMap || null,
|
|
1195
|
+
blackboardPath,
|
|
1189
1196
|
contextPack: scouts.contextPack || "",
|
|
1190
1197
|
synthesis: scouts.synthesis || "",
|
|
1198
|
+
blackboard: scouts.blackboard || null,
|
|
1191
1199
|
scouts: scouts.scouts.map((scout) => ({
|
|
1192
1200
|
name: scout.name,
|
|
1193
1201
|
model: scout.model,
|
|
@@ -1200,6 +1208,7 @@ export async function runAgent(config) {
|
|
|
1200
1208
|
model: scouts.model,
|
|
1201
1209
|
requested: scouts.requested,
|
|
1202
1210
|
completed: scouts.completed,
|
|
1211
|
+
blackboardPath,
|
|
1203
1212
|
});
|
|
1204
1213
|
emitConsole(config, `Parallel scouts: ${scouts.completed}/${scouts.requested} completed using ${scouts.model}`, {
|
|
1205
1214
|
kind: "meta",
|
package/src/capabilities.js
CHANGED
|
@@ -8,6 +8,7 @@ import { listProjectSessions, projectPaths, providerKeyStatus, readProjectInstru
|
|
|
8
8
|
import { listTaskProfiles } from "./task-profiles.js";
|
|
9
9
|
import { listAgentWrappers } from "./tool-wrappers.js";
|
|
10
10
|
import { listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
11
|
+
import { readCodebaseMap } from "./codebase-map.js";
|
|
11
12
|
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
|
|
@@ -110,7 +111,7 @@ function trustedDockerPolicyChecks(config) {
|
|
|
110
111
|
export async function buildCapabilityReport(projectRoot, packageVersion, config) {
|
|
111
112
|
const paths = projectPaths(projectRoot);
|
|
112
113
|
const keyStatus = providerKeyStatus(projectRoot);
|
|
113
|
-
const [node, npm, python, conda, r, pdflatex, latexmk, dockerStatus, sessions, instructions] = await Promise.all([
|
|
114
|
+
const [node, npm, python, conda, r, pdflatex, latexmk, dockerStatus, sessions, instructions, codebaseMap] = await Promise.all([
|
|
114
115
|
commandAvailable("node", ["--version"]),
|
|
115
116
|
commandAvailable("npm", ["--version"]),
|
|
116
117
|
commandAvailable("python3", ["--version"]),
|
|
@@ -121,6 +122,7 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
121
122
|
getDockerSandboxStatus(config).catch((error) => ({ ok: false, error: error.message })),
|
|
122
123
|
listProjectSessions(projectRoot, 12),
|
|
123
124
|
readProjectInstructions(projectRoot, { maxBytes: 1 }),
|
|
125
|
+
readCodebaseMap(projectRoot),
|
|
124
126
|
]);
|
|
125
127
|
|
|
126
128
|
const npmPrefixPolicy = evaluateCommandPolicy("npm --prefix round9-node-app test", config);
|
|
@@ -179,6 +181,13 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
179
181
|
sessionsDir: paths.sessionsDir,
|
|
180
182
|
sessionDbPath: paths.sessionDbPath,
|
|
181
183
|
sharedSessionFolder: path.resolve(config.sessionsDir) === path.resolve(paths.sessionsDir),
|
|
184
|
+
codebaseMap: {
|
|
185
|
+
present: Boolean(codebaseMap.ok),
|
|
186
|
+
path: paths.codebaseMapPath,
|
|
187
|
+
generatedAt: codebaseMap.ok ? codebaseMap.map.generatedAt || "" : "",
|
|
188
|
+
fingerprint: codebaseMap.ok ? codebaseMap.map.fingerprint || "" : "",
|
|
189
|
+
summary: codebaseMap.ok ? codebaseMap.map.inspection?.summary || "" : "",
|
|
190
|
+
},
|
|
182
191
|
},
|
|
183
192
|
routing: {
|
|
184
193
|
active: {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { projectPaths } from "./project.js";
|
|
5
|
+
import { redactSensitiveText } from "./redaction.js";
|
|
6
|
+
import { executeWorkspaceTool } from "./workspace-tools.js";
|
|
7
|
+
|
|
8
|
+
const MAP_VERSION = 1;
|
|
9
|
+
const DEFAULT_CONTEXT_PACK_CHARS = 3800;
|
|
10
|
+
|
|
11
|
+
function stableHash(value) {
|
|
12
|
+
return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function truncateText(text, limit = DEFAULT_CONTEXT_PACK_CHARS) {
|
|
16
|
+
const value = String(text || "");
|
|
17
|
+
if (value.length <= limit) return value;
|
|
18
|
+
return `${value.slice(0, Math.max(limit - 80, 1))}\n... [truncated ${value.length - limit + 80} chars]`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function listValues(items, limit, mapper) {
|
|
22
|
+
const values = Array.isArray(items) ? items.slice(0, limit).map(mapper).filter(Boolean) : [];
|
|
23
|
+
if (!values.length) return "";
|
|
24
|
+
const suffix = Array.isArray(items) && items.length > limit ? `, ... +${items.length - limit}` : "";
|
|
25
|
+
return `${values.join(", ")}${suffix}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function packageScriptValues(scripts) {
|
|
29
|
+
if (!Array.isArray(scripts) || !scripts.length) return "";
|
|
30
|
+
return listValues(scripts, 14, (item) => `${item.name}: ${item.command}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function compactInspection(inspected) {
|
|
34
|
+
return {
|
|
35
|
+
summary: inspected.summary || "",
|
|
36
|
+
counts: inspected.counts || {},
|
|
37
|
+
truncated: Boolean(inspected.truncated),
|
|
38
|
+
topLevel: Array.isArray(inspected.topLevel) ? inspected.topLevel.slice(0, 80) : [],
|
|
39
|
+
manifestFiles: Array.isArray(inspected.manifestFiles) ? inspected.manifestFiles.slice(0, 80) : [],
|
|
40
|
+
sourceDirs: Array.isArray(inspected.sourceDirs) ? inspected.sourceDirs.slice(0, 60) : [],
|
|
41
|
+
testFiles: Array.isArray(inspected.testFiles) ? inspected.testFiles.slice(0, 80) : [],
|
|
42
|
+
packageManagers: Array.isArray(inspected.packageManagers) ? inspected.packageManagers.slice(0, 20) : [],
|
|
43
|
+
packageScripts: Array.isArray(inspected.packageScripts) ? inspected.packageScripts.slice(0, 30) : [],
|
|
44
|
+
languageCounts: Array.isArray(inspected.languageCounts) ? inspected.languageCounts.slice(0, 30) : [],
|
|
45
|
+
git: inspected.git || {},
|
|
46
|
+
recommendedReads: Array.isArray(inspected.recommendedReads) ? inspected.recommendedReads.slice(0, 40) : [],
|
|
47
|
+
engineeringHints: Array.isArray(inspected.engineeringHints) ? inspected.engineeringHints.slice(0, 20) : [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function codebaseMapToContextPack(map, { maxChars = DEFAULT_CONTEXT_PACK_CHARS } = {}) {
|
|
52
|
+
if (!map?.inspection) return "Context pack: no durable codebase map available.";
|
|
53
|
+
const inspected = map.inspection;
|
|
54
|
+
const sections = [
|
|
55
|
+
`Context pack for ${map.commandCwd || map.projectRoot}`,
|
|
56
|
+
`Map: version=${map.version} generatedAt=${map.generatedAt} fingerprint=${map.fingerprint}`,
|
|
57
|
+
`Summary: ${inspected.summary || "no summary"}`,
|
|
58
|
+
inspected.counts
|
|
59
|
+
? `Counts: files=${inspected.counts.files} dirs=${inspected.counts.directories} bytes=${inspected.counts.totalBytes}`
|
|
60
|
+
: "",
|
|
61
|
+
`Top level: ${listValues(inspected.topLevel, 20, (item) => `${item.type}:${item.path}`)}`,
|
|
62
|
+
`Manifests: ${listValues(inspected.manifestFiles, 18, (item) => item.path)}`,
|
|
63
|
+
`Source dirs: ${listValues(inspected.sourceDirs, 14, (item) => item.path)}`,
|
|
64
|
+
`Tests: ${listValues(inspected.testFiles, 18, (item) => item.path)}`,
|
|
65
|
+
`Package managers: ${listValues(inspected.packageManagers, 8, (item) => item.name || item.path || item)}`,
|
|
66
|
+
packageScriptValues(inspected.packageScripts) ? `Package scripts: ${packageScriptValues(inspected.packageScripts)}` : "",
|
|
67
|
+
`Languages: ${listValues(inspected.languageCounts, 10, (item) => `${item.name}:${item.count}`)}`,
|
|
68
|
+
inspected.git?.present
|
|
69
|
+
? `Git: present; start with ${inspected.git.recommendedCommands?.join(" && ") || "git status --short"}; ${inspected.git.workflow}`
|
|
70
|
+
: "Git: not detected at workspace root.",
|
|
71
|
+
`Recommended reads: ${listValues(inspected.recommendedReads, 16, (item) => item)}`,
|
|
72
|
+
`Engineering hints: ${listValues(inspected.engineeringHints, 8, (item) => item)}`,
|
|
73
|
+
].filter(Boolean);
|
|
74
|
+
return truncateText(redactSensitiveText(sections.join("\n")), maxChars);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function readCodebaseMap(projectRoot = process.cwd()) {
|
|
78
|
+
const paths = projectPaths(projectRoot);
|
|
79
|
+
try {
|
|
80
|
+
const raw = await fs.readFile(paths.codebaseMapPath, "utf8");
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
path: paths.codebaseMapPath,
|
|
84
|
+
map: JSON.parse(raw),
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
path: paths.codebaseMapPath,
|
|
90
|
+
reason: error?.code === "ENOENT" ? "missing" : error instanceof Error ? error.message : String(error),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function refreshCodebaseMap(config, options = {}) {
|
|
96
|
+
if (!config.allowFileTools) {
|
|
97
|
+
return { ok: false, reason: "workspace file tools disabled" };
|
|
98
|
+
}
|
|
99
|
+
const projectRoot = path.resolve(options.projectRoot || config.commandCwd || config.baseDir || process.cwd());
|
|
100
|
+
const paths = projectPaths(projectRoot);
|
|
101
|
+
const inspected = await executeWorkspaceTool(
|
|
102
|
+
"inspect_project",
|
|
103
|
+
{
|
|
104
|
+
path: options.path || ".",
|
|
105
|
+
maxDepth: options.maxDepth || 4,
|
|
106
|
+
limit: options.limit || 1000,
|
|
107
|
+
includeFiles: Boolean(options.includeFiles),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
...config,
|
|
111
|
+
commandCwd: projectRoot,
|
|
112
|
+
allowFileTools: true,
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (!inspected?.ok) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
path: paths.codebaseMapPath,
|
|
120
|
+
reason: inspected?.reason || inspected?.error || "inspect_project failed",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const inspection = compactInspection(inspected);
|
|
125
|
+
const fingerprint = stableHash({
|
|
126
|
+
summary: inspection.summary,
|
|
127
|
+
counts: inspection.counts,
|
|
128
|
+
manifests: inspection.manifestFiles.map((item) => item.path),
|
|
129
|
+
sourceDirs: inspection.sourceDirs.map((item) => item.path),
|
|
130
|
+
tests: inspection.testFiles.map((item) => item.path),
|
|
131
|
+
packageScripts: inspection.packageScripts,
|
|
132
|
+
languages: inspection.languageCounts,
|
|
133
|
+
});
|
|
134
|
+
const map = {
|
|
135
|
+
version: MAP_VERSION,
|
|
136
|
+
generatedAt: new Date().toISOString(),
|
|
137
|
+
projectRoot: paths.root,
|
|
138
|
+
commandCwd: projectRoot,
|
|
139
|
+
fingerprint,
|
|
140
|
+
inspection,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
await fs.mkdir(paths.controlDir, { recursive: true });
|
|
144
|
+
await fs.writeFile(paths.codebaseMapPath, `${JSON.stringify(map, null, 2)}\n`, "utf8");
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
path: paths.codebaseMapPath,
|
|
148
|
+
map,
|
|
149
|
+
contextPack: codebaseMapToContextPack(map),
|
|
150
|
+
};
|
|
151
|
+
}
|
package/src/parallel-scouts.js
CHANGED
|
@@ -2,7 +2,7 @@ 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 {
|
|
5
|
+
import { refreshCodebaseMap } from "./codebase-map.js";
|
|
6
6
|
|
|
7
7
|
const MAX_CONTEXT_PACK_CHARS = 3800;
|
|
8
8
|
const MAX_SCOUT_CONTENT_CHARS = 1200;
|
|
@@ -94,60 +94,36 @@ function truncateText(text, limit = MAX_CONTEXT_PACK_CHARS) {
|
|
|
94
94
|
return `${value.slice(0, limit - 80)}\n... [truncated ${value.length - limit + 80} chars]`;
|
|
95
95
|
}
|
|
96
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
97
|
async function buildScoutContextPack(config) {
|
|
110
98
|
if (!config.allowFileTools) {
|
|
111
|
-
return
|
|
99
|
+
return {
|
|
100
|
+
text: "Context pack: workspace file tools are disabled. Scouts should recommend the minimal inspection commands/files instead of assuming repository structure.",
|
|
101
|
+
codebaseMap: null,
|
|
102
|
+
};
|
|
112
103
|
}
|
|
113
104
|
|
|
114
105
|
try {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
if (!inspected?.ok) {
|
|
124
|
-
return `Context pack: inspect_project unavailable (${inspected?.reason || inspected?.error || "unknown error"}).`;
|
|
106
|
+
const refreshed = await refreshCodebaseMap(config, { maxDepth: 4, limit: 1000, includeFiles: false });
|
|
107
|
+
if (!refreshed?.ok) {
|
|
108
|
+
return {
|
|
109
|
+
text: `Context pack: inspect_project unavailable (${refreshed?.reason || refreshed?.error || "unknown error"}).`,
|
|
110
|
+
codebaseMap: null,
|
|
111
|
+
};
|
|
125
112
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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")));
|
|
113
|
+
return {
|
|
114
|
+
text: truncateText(redactSensitiveText(refreshed.contextPack || "")),
|
|
115
|
+
codebaseMap: {
|
|
116
|
+
path: refreshed.path,
|
|
117
|
+
generatedAt: refreshed.map.generatedAt,
|
|
118
|
+
fingerprint: refreshed.map.fingerprint,
|
|
119
|
+
summary: refreshed.map.inspection?.summary || "",
|
|
120
|
+
},
|
|
121
|
+
};
|
|
149
122
|
} catch (error) {
|
|
150
|
-
return
|
|
123
|
+
return {
|
|
124
|
+
text: `Context pack: inspect_project failed (${redactSensitiveText(error instanceof Error ? error.message : String(error))}).`,
|
|
125
|
+
codebaseMap: null,
|
|
126
|
+
};
|
|
151
127
|
}
|
|
152
128
|
}
|
|
153
129
|
|
|
@@ -212,11 +188,44 @@ async function synthesizeScouts(client, config, model, scouts, contextPack) {
|
|
|
212
188
|
return redactSensitiveText(response.choices[0]?.message?.content || "").trim();
|
|
213
189
|
}
|
|
214
190
|
|
|
191
|
+
function extractFindings(content) {
|
|
192
|
+
return String(content || "")
|
|
193
|
+
.split(/\r?\n/)
|
|
194
|
+
.map((line) => line.trim().replace(/^[-*]\s+/, "").replace(/^\d+\.\s+/, ""))
|
|
195
|
+
.filter((line) => line && line.length > 8)
|
|
196
|
+
.slice(0, 6);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function buildScoutBlackboard({ config, state, model, requested, completed, scouts, contextPack, codebaseMap, synthesis }) {
|
|
200
|
+
return {
|
|
201
|
+
version: 1,
|
|
202
|
+
generatedAt: new Date().toISOString(),
|
|
203
|
+
goal: config.goal || state.goal || "",
|
|
204
|
+
model,
|
|
205
|
+
requested,
|
|
206
|
+
completed,
|
|
207
|
+
codebaseMap,
|
|
208
|
+
contextPack,
|
|
209
|
+
coordinator: synthesis || "",
|
|
210
|
+
lanes: scouts.map((scout) => ({
|
|
211
|
+
role: scout.name,
|
|
212
|
+
model: scout.model || model,
|
|
213
|
+
status: scout.content ? "completed" : "failed",
|
|
214
|
+
findings: scout.content ? extractFindings(scout.content) : [],
|
|
215
|
+
content: scout.content || "",
|
|
216
|
+
error: scout.error || "",
|
|
217
|
+
})),
|
|
218
|
+
handoff:
|
|
219
|
+
"Use this blackboard as advisory shared context. The executor must still inspect exact files, patch deterministically, run focused checks, and stop on conflicts or unrelated dirty work.",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
215
223
|
export async function runParallelScouts(client, config, state) {
|
|
216
224
|
const count = Math.min(Math.max(Number(config.parallelScoutCount) || 3, 1), SCOUTS.length);
|
|
217
225
|
const selected = SCOUTS.slice(0, count);
|
|
218
226
|
const model = scoutModel(config);
|
|
219
|
-
const
|
|
227
|
+
const context = await buildScoutContextPack(config);
|
|
228
|
+
const contextPack = context.text;
|
|
220
229
|
const settled = await Promise.allSettled(
|
|
221
230
|
selected.map(async (scout) => {
|
|
222
231
|
const response = await client.chat.completions.create(
|
|
@@ -260,6 +269,17 @@ export async function runParallelScouts(client, config, state) {
|
|
|
260
269
|
]
|
|
261
270
|
.filter(Boolean)
|
|
262
271
|
.join("\n");
|
|
272
|
+
const blackboard = buildScoutBlackboard({
|
|
273
|
+
config,
|
|
274
|
+
state,
|
|
275
|
+
model,
|
|
276
|
+
requested: selected.length,
|
|
277
|
+
completed,
|
|
278
|
+
scouts,
|
|
279
|
+
contextPack,
|
|
280
|
+
codebaseMap: context.codebaseMap,
|
|
281
|
+
synthesis,
|
|
282
|
+
});
|
|
263
283
|
|
|
264
284
|
return {
|
|
265
285
|
ok: completed > 0,
|
|
@@ -268,6 +288,8 @@ export async function runParallelScouts(client, config, state) {
|
|
|
268
288
|
completed,
|
|
269
289
|
scouts,
|
|
270
290
|
contextPack,
|
|
291
|
+
codebaseMap: context.codebaseMap,
|
|
292
|
+
blackboard,
|
|
271
293
|
synthesis,
|
|
272
294
|
summary,
|
|
273
295
|
};
|
package/src/project.js
CHANGED
|
@@ -33,6 +33,7 @@ export function projectPaths(projectRoot = process.cwd()) {
|
|
|
33
33
|
rootEnvPath: path.join(root, ".env"),
|
|
34
34
|
envExamplePath: path.join(root, ".aginti", ".env.example"),
|
|
35
35
|
controlReadmePath: path.join(root, ".aginti", "README.md"),
|
|
36
|
+
codebaseMapPath: path.join(root, ".aginti", "codebase-map.json"),
|
|
36
37
|
notesDir: path.join(root, "notes"),
|
|
37
38
|
notesReadmePath: path.join(root, "notes", "README.md"),
|
|
38
39
|
sessionsDir: path.join(root, ".sessions"),
|
|
@@ -195,6 +196,7 @@ export async function initProject(projectRoot = process.cwd()) {
|
|
|
195
196
|
"- `../AGINTI.md` stores editable project instructions for CLI and web agents.",
|
|
196
197
|
"- `.env` is ignored and can hold local provider keys.",
|
|
197
198
|
"- `.env.example` documents accepted variable names.",
|
|
199
|
+
"- `codebase-map.json` is a generated, ignored project-intelligence cache.",
|
|
198
200
|
"- `.sessions/` at the project root stores CLI and web run history.",
|
|
199
201
|
"",
|
|
200
202
|
].join("\n")
|
|
@@ -227,6 +229,7 @@ export async function initProject(projectRoot = process.cwd()) {
|
|
|
227
229
|
".aginti/.env",
|
|
228
230
|
".aginti/.env.*",
|
|
229
231
|
"!.aginti/.env.example",
|
|
232
|
+
".aginti/codebase-map.json",
|
|
230
233
|
".sessions/",
|
|
231
234
|
]);
|
|
232
235
|
if (gitignore.changed) updated.push(paths.gitignorePath);
|
package/src/session-store.js
CHANGED
|
@@ -38,6 +38,15 @@ export class SessionStore {
|
|
|
38
38
|
await fs.writeFile(this.planPath, `${planText.trim()}\n`, "utf8");
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
async saveJsonArtifact(filename, data) {
|
|
42
|
+
await this.ensure();
|
|
43
|
+
const safeName = path.basename(String(filename || "artifact.json"));
|
|
44
|
+
const outputName = safeName.endsWith(".json") ? safeName : `${safeName}.json`;
|
|
45
|
+
const filePath = path.join(this.artifactsDir, outputName);
|
|
46
|
+
await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
47
|
+
return filePath;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
async appendEvent(type, data = {}) {
|
|
42
51
|
await this.ensure();
|
|
43
52
|
const line = JSON.stringify({
|