@praneeth_54/agentdoctor 3.0.1 → 3.0.3
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/CHANGELOG.md +54 -1
- package/README.md +11 -9
- package/dist/agent/chat/deterministic.d.ts +2 -0
- package/dist/agent/chat/deterministic.js +112 -6
- package/dist/agent/chat/response.js +25 -4
- package/dist/agent/chat/service.d.ts +1 -0
- package/dist/agent/chat/service.js +28 -10
- package/dist/agent/chat/types.d.ts +3 -0
- package/dist/agent/context/brain-evidence.d.ts +26 -0
- package/dist/agent/context/brain-evidence.js +296 -0
- package/dist/agent/context/retrieve.d.ts +7 -1
- package/dist/agent/context/retrieve.js +204 -63
- package/dist/agent/progress.d.ts +10 -0
- package/dist/agent/progress.js +4 -0
- package/dist/cli/ask-progress.d.ts +17 -0
- package/dist/cli/ask-progress.js +85 -0
- package/dist/cli/commands/chat.d.ts +6 -0
- package/dist/cli/commands/chat.js +16 -0
- package/dist/cli/commands/start.js +15 -6
- package/dist/cli/program.js +2 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/core/understanding/brain/storage/store.js +0 -1
- package/dist/dashboard/server.js +11 -10
- package/dist/product/discovery/roots.js +115 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.0.3] — 2026-09-27
|
|
11
|
+
|
|
12
|
+
Patch release on top of public **3.0.2**: Brain → Project Chat evidence bridge and
|
|
13
|
+
real operation-based progress UX for `agentdoctor ask`.
|
|
14
|
+
|
|
15
|
+
Public relationship:
|
|
16
|
+
|
|
17
|
+
- **3.0.2** remains the immutable public discovery patch (`v3.0.2` → `8d41936`).
|
|
18
|
+
- **3.0.3** packages the finalize work from `a586cdf` (Brain→Chat + ask progress).
|
|
19
|
+
- Does **not** rewrite **3.0.0**, **3.0.1**, or **3.0.2** history.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Deterministic `agentdoctor ask` / Project Chat now consumes searchable Project Brain claims/components as first-class **INFERRED** evidence (aligned with `brain search`).
|
|
24
|
+
- Brain-derived source paths are ownership-gated before excerpts; Brain claims are never auto-upgraded to **VERIFIED**.
|
|
25
|
+
- `.agentdoctor/**` control-plane paths cannot become **VERIFIED** application source evidence.
|
|
26
|
+
- Architecture/module answers prioritize Brain + owned sources over generic minified graph noise.
|
|
27
|
+
- Ownership, containment, HOME/broad-root, `.private`, nested-repo, symlink, and stale-brain protections remain enforced.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `agentdoctor ask` progress UX driven by real pipeline stages (understand → search → retrieve → verify → prepare).
|
|
32
|
+
- Progress writes to stderr (TTY spinner / non-TTY stable lines); answers and `--json` stay on stdout without contamination.
|
|
33
|
+
- No artificial delays — spinner frames only animate while real work is in flight.
|
|
34
|
+
|
|
35
|
+
### Tests
|
|
36
|
+
|
|
37
|
+
- Added `tests/unit/agent/chat-brain-context.test.ts` for Brain → ask evidence, control-plane hostility, stale brain, and UNKNOWN.
|
|
38
|
+
- Added `tests/unit/cli/ask-progress.test.ts` for TTY/non-TTY/JSON/error progress lifecycle.
|
|
39
|
+
|
|
40
|
+
## [3.0.2] — 2026-09-27
|
|
41
|
+
|
|
42
|
+
Patch release: fix current-directory project discovery for `agentdoctor start`.
|
|
43
|
+
|
|
44
|
+
Public relationship:
|
|
45
|
+
|
|
46
|
+
- **3.0.0** and **3.0.1** remain immutable public releases.
|
|
47
|
+
- **3.0.2** is the public discovery patch (`v3.0.2` → `8d41936`). Brain→Chat and ask progress ship in **3.0.3**.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- `agentdoctor start` no longer reports `(none found)` for valid software trees that lack classic manifests (e.g. language-only roots without `package.json` / `.git`).
|
|
52
|
+
- Discovery fallback now reuses the same deterministic `detectProject` engine as `dna`, so `start` and `dna` agree on the project root.
|
|
53
|
+
- Clearer discovery UX (`✓ Project detected`) and empty-state guidance.
|
|
54
|
+
- Reject empty trees that only report `unknown` language; do not silently absorb multi-project parents with 2+ child marker projects.
|
|
55
|
+
- Ownership, containment, HOME/broad-root, `.private`, nested-repo, and symlink protections remain enforced.
|
|
56
|
+
|
|
57
|
+
### Tests
|
|
58
|
+
|
|
59
|
+
- Added `tests/unit/project/start-discovery.test.ts` regression coverage for markerless cwd, nested roots, multi-project parents, `.private`, and home refusal.
|
|
60
|
+
|
|
10
61
|
## [3.0.1] — 2026-09-27
|
|
11
62
|
|
|
12
63
|
Production-hardening follow-up on the reviewed freeze remediation commit
|
|
@@ -601,7 +652,9 @@ First public beta.
|
|
|
601
652
|
- Not a complete secret scanner
|
|
602
653
|
- Git “tracked secret” detection deferred
|
|
603
654
|
|
|
604
|
-
[Unreleased]: https://github.com/pranee54/AgentDoctor/compare/v3.0.
|
|
655
|
+
[Unreleased]: https://github.com/pranee54/AgentDoctor/compare/v3.0.3...HEAD
|
|
656
|
+
[3.0.3]: https://github.com/pranee54/AgentDoctor/compare/v3.0.2...v3.0.3
|
|
657
|
+
[3.0.2]: https://github.com/pranee54/AgentDoctor/compare/v3.0.1...v3.0.2
|
|
605
658
|
[3.0.1]: https://github.com/pranee54/AgentDoctor/compare/v3.0.0...v3.0.1
|
|
606
659
|
[3.0.0]: https://github.com/pranee54/AgentDoctor/compare/v2.1.0...v3.0.0
|
|
607
660
|
[2.1.0]: https://github.com/pranee54/AgentDoctor/compare/v2.0.1...v2.1.0
|
package/README.md
CHANGED
|
@@ -281,15 +281,15 @@ Student path: `learn` → ask → viva → docs → Build With Me (with `--appro
|
|
|
281
281
|
Requires **Node.js 20+**.
|
|
282
282
|
|
|
283
283
|
```bash
|
|
284
|
-
npm install -g @praneeth_54/agentdoctor@3.0.
|
|
285
|
-
agentdoctor --version # 3.0.
|
|
284
|
+
npm install -g @praneeth_54/agentdoctor@3.0.3
|
|
285
|
+
agentdoctor --version # 3.0.3
|
|
286
286
|
agentdoctor --help
|
|
287
287
|
```
|
|
288
288
|
|
|
289
289
|
Or without global install:
|
|
290
290
|
|
|
291
291
|
```bash
|
|
292
|
-
npx @praneeth_54/agentdoctor@3.0.
|
|
292
|
+
npx @praneeth_54/agentdoctor@3.0.3 --help
|
|
293
293
|
```
|
|
294
294
|
|
|
295
295
|
---
|
|
@@ -298,7 +298,7 @@ npx @praneeth_54/agentdoctor@3.0.1 --help
|
|
|
298
298
|
|
|
299
299
|
```bash
|
|
300
300
|
# 1. Install
|
|
301
|
-
npm install -g @praneeth_54/agentdoctor@3.0.
|
|
301
|
+
npm install -g @praneeth_54/agentdoctor@3.0.3
|
|
302
302
|
|
|
303
303
|
# 2. Enter YOUR project (not your home folder)
|
|
304
304
|
cd /path/to/my-project
|
|
@@ -581,11 +581,13 @@ Post-acceptance ideas for later releases: maintainers see [docs/internal/POST_3_
|
|
|
581
581
|
|
|
582
582
|
## Roadmap
|
|
583
583
|
|
|
584
|
-
| Track
|
|
585
|
-
|
|
|
586
|
-
| **3.0.0 (released)**
|
|
587
|
-
| **3.0.1 (
|
|
588
|
-
| **
|
|
584
|
+
| Track | Meaning |
|
|
585
|
+
| -------------------- | ------------------------------------------------------------------------ |
|
|
586
|
+
| **3.0.0 (released)** | Original public release (`v3.0.0` → `c21faf1`) |
|
|
587
|
+
| **3.0.1 (released)** | Freeze remediation / ownership & security hardening follow-up |
|
|
588
|
+
| **3.0.2 (released)** | `start` discovery aligned with deterministic `detectProject` |
|
|
589
|
+
| **3.0.3 (released)** | Brain → Project Chat evidence + `ask` progress UX |
|
|
590
|
+
| **Coming soon** | Maturity, language depth, runtime adapters, DX — not promised ship dates |
|
|
589
591
|
|
|
590
592
|
---
|
|
591
593
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ChatTurnResponse } from "./types.js";
|
|
2
|
+
import { type AskProgressReporter } from "../progress.js";
|
|
2
3
|
export declare function answerDeterministicProjectQuestion(options: {
|
|
3
4
|
root: string;
|
|
4
5
|
question: string;
|
|
5
6
|
sessionId: string;
|
|
7
|
+
progress?: AskProgressReporter;
|
|
6
8
|
}): Promise<ChatTurnResponse>;
|
|
@@ -4,20 +4,69 @@ import { analyzeDependencies } from "../../product/deps/analyze.js";
|
|
|
4
4
|
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
5
5
|
import { resolveRepoRoot } from "../../utils/path.js";
|
|
6
6
|
import { retrieveProjectContext } from "../context/retrieve.js";
|
|
7
|
+
import { extractQueryTerms } from "../context/brain-evidence.js";
|
|
7
8
|
import { buildChatTurnResponse } from "./response.js";
|
|
8
|
-
|
|
9
|
+
import { noopAskProgress } from "../progress.js";
|
|
10
|
+
const ARCH_RE = /architecture|structure|module|layout|what (is|does) (this|my) project|project overview|understand (my |this )?project/i;
|
|
9
11
|
const AUTH_RE = /auth|login|password|session|jwt|oauth/i;
|
|
10
12
|
const DEPS_RE = /depend|package|lockfile|npm|yarn|pnpm/i;
|
|
11
|
-
const START_RE = /where.*(start|entry|boot|main)|application start|entry\s*point/i;
|
|
12
13
|
const DB_RE = /database|db\.|sql|prisma|query\(/i;
|
|
14
|
+
const MODULE_RE = /\bmodule\b|\bcomponent\b|\bprovides?\b|\bbelong/i;
|
|
15
|
+
/** ReDoS-safe: avoid `where.*…` / `how.*…` backtracking on uncontrolled chat input. */
|
|
16
|
+
function isStartQuestion(q) {
|
|
17
|
+
if (/application start|entry\s*point/i.test(q))
|
|
18
|
+
return true;
|
|
19
|
+
const lower = q.toLowerCase();
|
|
20
|
+
if (!lower.includes("where"))
|
|
21
|
+
return false;
|
|
22
|
+
return /\b(start|entry|boot|main)\b/i.test(q);
|
|
23
|
+
}
|
|
24
|
+
function isArchitectureQuestion(q) {
|
|
25
|
+
if (ARCH_RE.test(q))
|
|
26
|
+
return true;
|
|
27
|
+
const lower = q.toLowerCase();
|
|
28
|
+
if (lower.includes("how") && /(?:project|repo|codebase)/i.test(q))
|
|
29
|
+
return true;
|
|
30
|
+
if (lower.includes("explain") && /(?:project|repo|codebase|\bthis\b)/i.test(q))
|
|
31
|
+
return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function isModuleQuestion(q) {
|
|
35
|
+
return MODULE_RE.test(q) || extractQueryTerms(q).length > 0;
|
|
36
|
+
}
|
|
13
37
|
export async function answerDeterministicProjectQuestion(options) {
|
|
14
38
|
const root = resolveRepoRoot(options.root);
|
|
15
39
|
const q = options.question.trim();
|
|
40
|
+
const progress = options.progress ?? noopAskProgress;
|
|
41
|
+
progress.stage("understand", "start");
|
|
42
|
+
const terms = extractQueryTerms(q);
|
|
43
|
+
progress.stage("understand", "ok", terms.length ? `focus: ${terms.slice(0, 4).join(", ")}` : undefined);
|
|
16
44
|
const context = await retrieveProjectContext({
|
|
17
45
|
root,
|
|
18
46
|
query: q,
|
|
19
47
|
budgetTokens: 4_000,
|
|
48
|
+
maxSourceFiles: 8,
|
|
49
|
+
progress,
|
|
20
50
|
});
|
|
51
|
+
progress.stage("prepare", "start");
|
|
52
|
+
try {
|
|
53
|
+
const response = await buildDeterministicAnswer({
|
|
54
|
+
root,
|
|
55
|
+
q,
|
|
56
|
+
terms,
|
|
57
|
+
context,
|
|
58
|
+
sessionId: options.sessionId,
|
|
59
|
+
});
|
|
60
|
+
progress.stage("prepare", "ok");
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
progress.stage("prepare", "fail", error instanceof Error ? error.message : String(error));
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function buildDeterministicAnswer(options) {
|
|
69
|
+
const { root, q, terms, context, sessionId } = options;
|
|
21
70
|
const sections = [
|
|
22
71
|
"Deterministic project answer (no LLM). Evidence from DNA, graph, brain, and dependency analyzers.",
|
|
23
72
|
"",
|
|
@@ -28,6 +77,27 @@ export async function answerDeterministicProjectQuestion(options) {
|
|
|
28
77
|
sections.push(`Frameworks: ${dna.frameworks.join(", ") || "UNKNOWN"}`);
|
|
29
78
|
sections.push(`Monorepo: ${dna.monorepo.isMonorepo ? dna.monorepo.tool : "no"}`);
|
|
30
79
|
sections.push("");
|
|
80
|
+
const brainCitations = context.citations.filter((c) => c.source === "brain" || c.evidenceType === "project-brain");
|
|
81
|
+
const sourceCitations = context.citations.filter((c) => c.confidence === "VERIFIED" && c.evidenceType === "source-code" && c.path);
|
|
82
|
+
if (brainCitations.length > 0 && (isModuleQuestion(q) || isArchitectureQuestion(q))) {
|
|
83
|
+
const focus = terms.length ? terms.join(", ") : "project";
|
|
84
|
+
sections.push(`[INFERRED] Project Brain evidence for: ${focus}`);
|
|
85
|
+
for (const c of brainCitations.slice(0, 14)) {
|
|
86
|
+
sections.push(` - ${c.excerpt ?? c.note ?? "brain claim"}`);
|
|
87
|
+
}
|
|
88
|
+
sections.push("");
|
|
89
|
+
if (sourceCitations.length) {
|
|
90
|
+
sections.push("[VERIFIED] Owned source paths linked from Brain / retrieval:");
|
|
91
|
+
for (const c of sourceCitations.slice(0, 10)) {
|
|
92
|
+
sections.push(` - ${c.path}`);
|
|
93
|
+
}
|
|
94
|
+
sections.push("");
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
sections.push("[INFERRED] No owned source excerpts resolved for these Brain claims; structural evidence only.");
|
|
98
|
+
sections.push("");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
31
101
|
if (AUTH_RE.test(q)) {
|
|
32
102
|
const authFiles = context.citations
|
|
33
103
|
.filter((c) => c.path && /auth|login|session|password/i.test(c.path))
|
|
@@ -61,7 +131,7 @@ export async function answerDeterministicProjectQuestion(options) {
|
|
|
61
131
|
}
|
|
62
132
|
sections.push("");
|
|
63
133
|
}
|
|
64
|
-
if (
|
|
134
|
+
if (isStartQuestion(q)) {
|
|
65
135
|
const startHits = context.citations
|
|
66
136
|
.filter((c) => c.path && /(server|index|main|app)\.[jt]sx?$/i.test(c.path))
|
|
67
137
|
.slice(0, 6);
|
|
@@ -139,11 +209,28 @@ export async function answerDeterministicProjectQuestion(options) {
|
|
|
139
209
|
sections.push(`Lockfiles: ${deps.lockfilesPresent.join(", ") || "none"}`);
|
|
140
210
|
sections.push("");
|
|
141
211
|
}
|
|
142
|
-
if (
|
|
212
|
+
if (isArchitectureQuestion(q) && brainCitations.length === 0) {
|
|
143
213
|
try {
|
|
144
214
|
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
145
215
|
sections.push(`[INFERRED] Graph: ${graph.nodes.length} nodes, ${graph.edges.length} edges (${graph.builder})`);
|
|
146
|
-
const
|
|
216
|
+
const termLower = terms.map((t) => t.toLowerCase());
|
|
217
|
+
const filtered = graph.nodes
|
|
218
|
+
.filter((n) => n.path && n.label)
|
|
219
|
+
.filter((n) => {
|
|
220
|
+
if (/\.min\.js$/i.test(n.path) || /chart\.umd|node_modules/i.test(n.path))
|
|
221
|
+
return false;
|
|
222
|
+
if (termLower.length === 0)
|
|
223
|
+
return n.kind === "file" || n.kind === "module";
|
|
224
|
+
const blob = `${n.label} ${n.path}`.toLowerCase();
|
|
225
|
+
return termLower.some((t) => blob.includes(t));
|
|
226
|
+
})
|
|
227
|
+
.slice(0, 8);
|
|
228
|
+
const sample = filtered.length
|
|
229
|
+
? filtered
|
|
230
|
+
: graph.nodes
|
|
231
|
+
.filter((n) => n.path && !/\.min\.js$/i.test(n.path))
|
|
232
|
+
.filter((n) => n.kind === "file")
|
|
233
|
+
.slice(0, 6);
|
|
147
234
|
for (const n of sample) {
|
|
148
235
|
sections.push(` - ${n.kind}: ${n.label}${n.path ? ` @ ${n.path}` : ""}`);
|
|
149
236
|
}
|
|
@@ -153,6 +240,17 @@ export async function answerDeterministicProjectQuestion(options) {
|
|
|
153
240
|
}
|
|
154
241
|
sections.push("");
|
|
155
242
|
}
|
|
243
|
+
else if (isArchitectureQuestion(q)) {
|
|
244
|
+
// Brain already rendered; add a short graph filter if present in context.
|
|
245
|
+
const graphCite = context.citations.find((c) => c.source === "graph");
|
|
246
|
+
if (graphCite?.excerpt) {
|
|
247
|
+
sections.push("[INFERRED] Query-filtered graph nodes:");
|
|
248
|
+
for (const line of graphCite.excerpt.split("\n").slice(0, 6)) {
|
|
249
|
+
sections.push(` - ${line}`);
|
|
250
|
+
}
|
|
251
|
+
sections.push("");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
156
254
|
const brain = await getBrainStatus(root);
|
|
157
255
|
if (brain.hasSnapshot) {
|
|
158
256
|
sections.push(`[INFERRED] Project brain snapshot present (${brain.snapshotCount} stored; latest ${brain.latestSnapshotId ?? "unknown"}).`);
|
|
@@ -160,6 +258,14 @@ export async function answerDeterministicProjectQuestion(options) {
|
|
|
160
258
|
else {
|
|
161
259
|
sections.push("[UNKNOWN] No project brain snapshot — run brain compile for richer answers.");
|
|
162
260
|
}
|
|
261
|
+
if (brainCitations.length === 0 &&
|
|
262
|
+
sourceCitations.length === 0 &&
|
|
263
|
+
!AUTH_RE.test(q) &&
|
|
264
|
+
!DEPS_RE.test(q) &&
|
|
265
|
+
!isArchitectureQuestion(q)) {
|
|
266
|
+
sections.push("");
|
|
267
|
+
sections.push("[UNKNOWN] Insufficient Brain/source evidence for this question — try a more specific module or path name.");
|
|
268
|
+
}
|
|
163
269
|
sections.push("");
|
|
164
270
|
sections.push("Limitations: deterministic answers cannot invent behavior not present in evidence.");
|
|
165
271
|
const bundle = {
|
|
@@ -170,7 +276,7 @@ export async function answerDeterministicProjectQuestion(options) {
|
|
|
170
276
|
],
|
|
171
277
|
};
|
|
172
278
|
return buildChatTurnResponse({
|
|
173
|
-
sessionId
|
|
279
|
+
sessionId,
|
|
174
280
|
modelText: sections.join("\n"),
|
|
175
281
|
context: bundle,
|
|
176
282
|
provider: "deterministic",
|
|
@@ -4,8 +4,12 @@ import { truthLabelHelp } from "../context/truth.js";
|
|
|
4
4
|
* Never invents line ranges. Citations come only from the context bundle.
|
|
5
5
|
*/
|
|
6
6
|
export function buildChatTurnResponse(options) {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const verifiedSource = options.context.citations.filter((c) => c.path && c.confidence === "VERIFIED" && c.evidenceType === "source-code");
|
|
8
|
+
const brainCitations = options.context.citations.filter((c) => c.source === "brain" || c.evidenceType === "project-brain");
|
|
9
|
+
const graphCitations = options.context.citations.filter((c) => c.source === "graph" || c.evidenceType === "graph");
|
|
10
|
+
// Surface verified source + brain/graph evidence (brain is never upgraded to VERIFIED).
|
|
11
|
+
const citations = [...verifiedSource, ...brainCitations, ...graphCitations];
|
|
12
|
+
const contextPaths = verifiedSource.map((c) => c.path).filter(Boolean);
|
|
9
13
|
const mentioned = extractMentionedPaths(options.modelText, contextPaths);
|
|
10
14
|
const truthClaims = [];
|
|
11
15
|
for (const path of mentioned) {
|
|
@@ -15,13 +19,21 @@ export function buildChatTurnResponse(options) {
|
|
|
15
19
|
citationPaths: [path],
|
|
16
20
|
});
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
const hasUsableEvidence = verifiedSource.length > 0 || brainCitations.length > 0;
|
|
23
|
+
if (!hasUsableEvidence) {
|
|
19
24
|
truthClaims.push({
|
|
20
25
|
text: "Insufficient repository evidence was retrieved for this question.",
|
|
21
26
|
label: "UNKNOWN",
|
|
22
27
|
citationPaths: [],
|
|
23
28
|
});
|
|
24
29
|
}
|
|
30
|
+
else if (brainCitations.length > 0 && verifiedSource.length === 0) {
|
|
31
|
+
truthClaims.push({
|
|
32
|
+
text: "Answer grounded in Project Brain structural evidence (INFERRED; no owned source excerpts).",
|
|
33
|
+
label: "INFERRED",
|
|
34
|
+
citationPaths: [],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
25
37
|
else if (options.modelText.trim()) {
|
|
26
38
|
truthClaims.push({
|
|
27
39
|
text: "Interpretive explanation based on retrieved project evidence.",
|
|
@@ -83,7 +95,16 @@ export function formatChatResponseForCli(response) {
|
|
|
83
95
|
}
|
|
84
96
|
if (response.citations.length > 0) {
|
|
85
97
|
lines.push("Evidence:");
|
|
86
|
-
for (const c of response.citations.slice(0,
|
|
98
|
+
for (const c of response.citations.slice(0, 16)) {
|
|
99
|
+
if (c.source === "brain" || c.evidenceType === "project-brain") {
|
|
100
|
+
lines.push(` - ${c.excerpt ?? c.note ?? "brain claim"} (${c.confidence})`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (c.source === "graph" || c.evidenceType === "graph") {
|
|
104
|
+
const first = (c.excerpt ?? c.note ?? "graph").split("\n")[0] ?? "graph";
|
|
105
|
+
lines.push(` - ${first} (${c.confidence})`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
87
108
|
const path = c.path ?? "(unknown)";
|
|
88
109
|
lines.push(` - ${path}${c.range ? `:${c.range}` : ""} (${c.confidence})`);
|
|
89
110
|
}
|
|
@@ -9,6 +9,7 @@ import { PROJECT_CHAT_SYSTEM_PROMPT, wrapProjectData } from "./prompts.js";
|
|
|
9
9
|
import { formatProjectSummary, summarizeProjectForChat, } from "./project-summary.js";
|
|
10
10
|
import { buildChatTurnResponse, formatChatResponseForCli } from "./response.js";
|
|
11
11
|
import { answerDeterministicProjectQuestion } from "./deterministic.js";
|
|
12
|
+
import { noopAskProgress } from "../progress.js";
|
|
12
13
|
export const CHAT_PROVIDER_NONE_MESSAGE = `AI chat is not configured.
|
|
13
14
|
|
|
14
15
|
Configure an AI provider to use AgentDoctor Project Chat.
|
|
@@ -28,6 +29,7 @@ export class ChatService {
|
|
|
28
29
|
budgetTokens;
|
|
29
30
|
persistAudit;
|
|
30
31
|
systemPrompt;
|
|
32
|
+
progress;
|
|
31
33
|
auditSession;
|
|
32
34
|
graphCache;
|
|
33
35
|
lastContext;
|
|
@@ -37,6 +39,7 @@ export class ChatService {
|
|
|
37
39
|
this.provider = options.provider ?? createModelProvider(loadAiConfig());
|
|
38
40
|
this.budgetTokens = options.budgetTokens ?? 6_000;
|
|
39
41
|
this.persistAudit = options.persistAudit !== false;
|
|
42
|
+
this.progress = options.progress ?? noopAskProgress;
|
|
40
43
|
this.systemPrompt = options.systemPromptAddon
|
|
41
44
|
? `${PROJECT_CHAT_SYSTEM_PROMPT}\n\n${options.systemPromptAddon}`
|
|
42
45
|
: PROJECT_CHAT_SYSTEM_PROMPT;
|
|
@@ -104,27 +107,40 @@ export class ChatService {
|
|
|
104
107
|
root: this.root,
|
|
105
108
|
question: safeUser,
|
|
106
109
|
sessionId: this.sessionId,
|
|
110
|
+
progress: this.progress,
|
|
107
111
|
});
|
|
112
|
+
this.lastContext = undefined;
|
|
108
113
|
this.memory.addAssistant(response.message, response.contextPaths);
|
|
109
114
|
await this.audit("prompt", "CHAT_DETERMINISTIC", { status: response.status });
|
|
110
115
|
return response;
|
|
111
116
|
}
|
|
117
|
+
this.progress.stage("understand", "start");
|
|
112
118
|
const query = this.memory.resolveQuery(safeUser);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
this.progress.stage("understand", "ok");
|
|
120
|
+
let context;
|
|
121
|
+
try {
|
|
122
|
+
context = await retrieveProjectContext({
|
|
123
|
+
root: this.root,
|
|
124
|
+
query,
|
|
125
|
+
budgetTokens: this.budgetTokens,
|
|
126
|
+
includePaths: [
|
|
127
|
+
...this.memory.getReferencedPaths().slice(-8),
|
|
128
|
+
...this.memory.getLastContextPaths().slice(-8),
|
|
129
|
+
],
|
|
130
|
+
...(this.graphCache ? { graph: this.graphCache } : {}),
|
|
131
|
+
progress: this.progress,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
this.progress.stop();
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
123
138
|
this.lastContext = context;
|
|
124
139
|
await this.audit("file-read", "CHAT_CONTEXT_RETRIEVED", {
|
|
125
140
|
citations: String(context.citations.length),
|
|
126
141
|
tokens: String(context.estimatedTokens),
|
|
127
142
|
});
|
|
143
|
+
this.progress.stage("prepare", "start");
|
|
128
144
|
const history = this.memory.historyForModel().slice(0, -1); // exclude current user (added separately)
|
|
129
145
|
const messages = [
|
|
130
146
|
{ role: "system", content: this.systemPrompt },
|
|
@@ -142,6 +158,7 @@ export class ChatService {
|
|
|
142
158
|
maxTokens: 2048,
|
|
143
159
|
});
|
|
144
160
|
if (modelResult.error) {
|
|
161
|
+
this.progress.stage("prepare", "fail", sanitizeProviderError(modelResult.error));
|
|
145
162
|
const response = buildChatTurnResponse({
|
|
146
163
|
sessionId: this.sessionId,
|
|
147
164
|
modelText: `AgentDoctor could not reach the configured AI provider.\n\nProvider: ${modelResult.provider}\nModel: ${modelResult.model}\nError: ${sanitizeProviderError(modelResult.error)}`,
|
|
@@ -158,6 +175,7 @@ export class ChatService {
|
|
|
158
175
|
});
|
|
159
176
|
return response;
|
|
160
177
|
}
|
|
178
|
+
this.progress.stage("prepare", "ok");
|
|
161
179
|
const text = redactForModel(modelResult.message.content || "");
|
|
162
180
|
const response = buildChatTurnResponse({
|
|
163
181
|
sessionId: this.sessionId,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TruthLabel, ContextCitation } from "../context/types.js";
|
|
2
2
|
import type { AiProviderId, ModelProvider, TokenUsage } from "../../ai/types.js";
|
|
3
|
+
import type { AskProgressReporter } from "../progress.js";
|
|
3
4
|
export type ChatRole = "system" | "user" | "assistant";
|
|
4
5
|
export interface ChatMessage {
|
|
5
6
|
role: ChatRole;
|
|
@@ -45,4 +46,6 @@ export interface ChatServiceOptions {
|
|
|
45
46
|
persistAudit?: boolean;
|
|
46
47
|
/** Appended to PROJECT_CHAT_SYSTEM_PROMPT (modes) */
|
|
47
48
|
systemPromptAddon?: string;
|
|
49
|
+
/** Optional ask progress reporter (CLI TTY UX). */
|
|
50
|
+
progress?: AskProgressReporter;
|
|
48
51
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type BrainSearchHit } from "../../core/brain-cli/service.js";
|
|
2
|
+
import type { ProjectBrain } from "../../core/understanding/brain/index.js";
|
|
3
|
+
import type { ContextCitation } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* AgentDoctor control-plane under `.agentdoctor/**` — usable internally,
|
|
6
|
+
* never as ordinary VERIFIED application source evidence.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isControlPlaneRelativePath(relativePath: string): boolean;
|
|
9
|
+
/** Significant tokens from a natural-language project question. */
|
|
10
|
+
export declare function extractQueryTerms(query: string): string[];
|
|
11
|
+
export interface BrainEvidencePack {
|
|
12
|
+
brain: ProjectBrain | null;
|
|
13
|
+
hits: BrainSearchHit[];
|
|
14
|
+
citations: ContextCitation[];
|
|
15
|
+
/** Owned-looking source paths derived from claims/components/evidence (not yet ownership-gated). */
|
|
16
|
+
candidateSourcePaths: string[];
|
|
17
|
+
limitations: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Search the latest usable Project Brain for query-relevant claims/components.
|
|
21
|
+
* Brain hits are INFERRED structural evidence — never auto-upgraded to VERIFIED.
|
|
22
|
+
* Stale ownership-boundary brains load as null (same as CLI brain status).
|
|
23
|
+
*/
|
|
24
|
+
export declare function collectBrainEvidenceForQuery(root: string, query: string, options?: {
|
|
25
|
+
maxHits?: number;
|
|
26
|
+
}): Promise<BrainEvidencePack>;
|