@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 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 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`.
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 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.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.12.11",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
6
6
  "license": "Apache-2.0",
@@ -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",
@@ -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",
@@ -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
+ }
@@ -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 { executeWorkspaceTool } from "./workspace-tools.js";
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 "Context pack: workspace file tools are disabled. Scouts should recommend the minimal inspection commands/files instead of assuming repository structure.";
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 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"}).`;
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
- 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")));
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 `Context pack: inspect_project failed (${redactSensitiveText(error instanceof Error ? error.message : String(error))}).`;
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 contextPack = await buildScoutContextPack(config);
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);
@@ -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({