@praneeth_54/agentdoctor 2.1.0 → 3.0.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/CHANGELOG.md +57 -3
- package/README.md +489 -290
- package/dist/agent/chat/deterministic.d.ts +6 -0
- package/dist/agent/chat/deterministic.js +180 -0
- package/dist/agent/chat/service.js +8 -14
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/loop.d.ts +2 -0
- package/dist/agent/loop.js +31 -0
- package/dist/agent/roles.d.ts +12 -0
- package/dist/agent/roles.js +138 -0
- package/dist/agent/runtime.d.ts +21 -5
- package/dist/agent/runtime.js +141 -28
- package/dist/agent/tools/execute.d.ts +2 -0
- package/dist/agent/tools/execute.js +10 -0
- package/dist/ai/config.js +1 -0
- package/dist/ai/redact.d.ts +1 -0
- package/dist/ai/redact.js +47 -7
- package/dist/ai/types.d.ts +1 -1
- package/dist/cli/commands/agent.d.ts +2 -0
- package/dist/cli/commands/agent.js +19 -8
- package/dist/cli/commands/chat.js +2 -4
- package/dist/cli/commands/product.d.ts +19 -0
- package/dist/cli/commands/product.js +147 -0
- package/dist/cli/commands/start.d.ts +15 -0
- package/dist/cli/commands/start.js +80 -0
- package/dist/cli/program.js +141 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/dashboard/server.js +255 -53
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/intelligence/graph/build.js +36 -8
- package/dist/intelligence/resolve/imports.js +1 -1
- package/dist/languages/dart.d.ts +10 -0
- package/dist/languages/dart.js +99 -0
- package/dist/languages/go.d.ts +13 -7
- package/dist/languages/go.js +84 -24
- package/dist/languages/index.d.ts +5 -1
- package/dist/languages/index.js +13 -36
- package/dist/languages/java.d.ts +10 -0
- package/dist/languages/java.js +80 -0
- package/dist/languages/kotlin.d.ts +10 -0
- package/dist/languages/kotlin.js +85 -0
- package/dist/languages/rust.d.ts +10 -0
- package/dist/languages/rust.js +93 -0
- package/dist/languages/types.d.ts +4 -3
- package/dist/mcp/agent/registry.d.ts +1 -1
- package/dist/mcp/agent/registry.js +109 -23
- package/dist/mcp/intelligence/handlers.d.ts +3 -0
- package/dist/mcp/intelligence/handlers.js +28 -0
- package/dist/mcp/intelligence/registry.d.ts +1 -1
- package/dist/mcp/intelligence/registry.js +30 -1
- package/dist/product/api/doctor.d.ts +14 -0
- package/dist/product/api/doctor.js +185 -0
- package/dist/product/api/openapi.d.ts +7 -0
- package/dist/product/api/openapi.js +122 -0
- package/dist/product/approval/model.d.ts +30 -0
- package/dist/product/approval/model.js +64 -0
- package/dist/product/approval/session.d.ts +49 -0
- package/dist/product/approval/session.js +134 -0
- package/dist/product/database/doctor.d.ts +30 -0
- package/dist/product/database/doctor.js +184 -0
- package/dist/product/decisions/ledger.d.ts +24 -0
- package/dist/product/decisions/ledger.js +110 -0
- package/dist/product/deps/analyze.d.ts +46 -0
- package/dist/product/deps/analyze.js +137 -0
- package/dist/product/deps/lockfiles.d.ts +25 -0
- package/dist/product/deps/lockfiles.js +200 -0
- package/dist/product/discovery/roots.d.ts +42 -0
- package/dist/product/discovery/roots.js +215 -0
- package/dist/product/dna/build.d.ts +50 -0
- package/dist/product/dna/build.js +255 -0
- package/dist/product/eval/lab.d.ts +17 -0
- package/dist/product/eval/lab.js +218 -0
- package/dist/product/events/doctor.d.ts +21 -0
- package/dist/product/events/doctor.js +147 -0
- package/dist/product/evidence-scan.d.ts +12 -0
- package/dist/product/evidence-scan.js +46 -0
- package/dist/product/evolution/timeline.d.ts +29 -0
- package/dist/product/evolution/timeline.js +123 -0
- package/dist/product/features/intelligence.d.ts +23 -0
- package/dist/product/features/intelligence.js +158 -0
- package/dist/product/forensic/mode.d.ts +25 -0
- package/dist/product/forensic/mode.js +70 -0
- package/dist/product/graph/enrich-languages.d.ts +20 -0
- package/dist/product/graph/enrich-languages.js +193 -0
- package/dist/product/health/code-health.d.ts +20 -0
- package/dist/product/health/code-health.js +149 -0
- package/dist/product/index.d.ts +69 -0
- package/dist/product/index.js +35 -0
- package/dist/product/ledger/change-ledger.d.ts +23 -0
- package/dist/product/ledger/change-ledger.js +64 -0
- package/dist/product/map/software-map.d.ts +17 -0
- package/dist/product/map/software-map.js +95 -0
- package/dist/product/memory/institutional.d.ts +16 -0
- package/dist/product/memory/institutional.js +87 -0
- package/dist/product/ops/incident.d.ts +20 -0
- package/dist/product/ops/incident.js +61 -0
- package/dist/product/ops/infra.d.ts +15 -0
- package/dist/product/ops/infra.js +112 -0
- package/dist/product/org/model.d.ts +34 -0
- package/dist/product/org/model.js +195 -0
- package/dist/product/privacy/doctor.d.ts +13 -0
- package/dist/product/privacy/doctor.js +90 -0
- package/dist/product/requirements/trace.d.ts +21 -0
- package/dist/product/requirements/trace.js +166 -0
- package/dist/product/search/index.d.ts +29 -0
- package/dist/product/search/index.js +116 -0
- package/dist/product/search/software-search.d.ts +19 -0
- package/dist/product/search/software-search.js +100 -0
- package/dist/product/security/doctor.d.ts +23 -0
- package/dist/product/security/doctor.js +124 -0
- package/dist/product/self/diagnose.d.ts +15 -0
- package/dist/product/self/diagnose.js +82 -0
- package/dist/product/techdebt/roadmap.d.ts +20 -0
- package/dist/product/techdebt/roadmap.js +118 -0
- package/dist/product/testbrain/analyze.d.ts +29 -0
- package/dist/product/testbrain/analyze.js +129 -0
- package/dist/product/truth.d.ts +12 -0
- package/dist/product/truth.js +30 -0
- package/dist/product/twin/digital-twin.d.ts +23 -0
- package/dist/product/twin/digital-twin.js +52 -0
- package/dist/product/twin/store.d.ts +21 -0
- package/dist/product/twin/store.js +70 -0
- package/dist/product/whatif/engine.d.ts +28 -0
- package/dist/product/whatif/engine.js +70 -0
- package/dist/utils/fs.js +10 -2
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { getBrainStatus } from "../../core/brain-cli/service.js";
|
|
2
|
+
import { buildProjectDna } from "../../product/dna/build.js";
|
|
3
|
+
import { analyzeDependencies } from "../../product/deps/analyze.js";
|
|
4
|
+
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
5
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
6
|
+
import { retrieveProjectContext } from "../context/retrieve.js";
|
|
7
|
+
import { buildChatTurnResponse } from "./response.js";
|
|
8
|
+
const ARCH_RE = /architecture|structure|module|layout|how.*(project|repo|codebase)|explain.*(project|repo|codebase|this)|what (is|does) (this|my) project|project overview|understand (my |this )?project/i;
|
|
9
|
+
const AUTH_RE = /auth|login|password|session|jwt|oauth/i;
|
|
10
|
+
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
|
+
const DB_RE = /database|db\.|sql|prisma|query\(/i;
|
|
13
|
+
export async function answerDeterministicProjectQuestion(options) {
|
|
14
|
+
const root = resolveRepoRoot(options.root);
|
|
15
|
+
const q = options.question.trim();
|
|
16
|
+
const context = await retrieveProjectContext({
|
|
17
|
+
root,
|
|
18
|
+
query: q,
|
|
19
|
+
budgetTokens: 4_000,
|
|
20
|
+
});
|
|
21
|
+
const sections = [
|
|
22
|
+
"Deterministic project answer (no LLM). Evidence from DNA, graph, brain, and dependency analyzers.",
|
|
23
|
+
"",
|
|
24
|
+
];
|
|
25
|
+
const dna = await buildProjectDna(root);
|
|
26
|
+
sections.push(`[VERIFIED] Project: ${dna.name}`);
|
|
27
|
+
sections.push(`Languages: ${dna.languages.join(", ") || "UNKNOWN"}`);
|
|
28
|
+
sections.push(`Frameworks: ${dna.frameworks.join(", ") || "UNKNOWN"}`);
|
|
29
|
+
sections.push(`Monorepo: ${dna.monorepo.isMonorepo ? dna.monorepo.tool : "no"}`);
|
|
30
|
+
sections.push("");
|
|
31
|
+
if (AUTH_RE.test(q)) {
|
|
32
|
+
const authFiles = context.citations
|
|
33
|
+
.filter((c) => c.path && /auth|login|session|password/i.test(c.path))
|
|
34
|
+
.slice(0, 8);
|
|
35
|
+
sections.push("[INFERRED] Authentication-related paths from retrieval:");
|
|
36
|
+
if (authFiles.length) {
|
|
37
|
+
for (const c of authFiles) {
|
|
38
|
+
sections.push(` - ${c.path} (${c.confidence})`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
try {
|
|
43
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
44
|
+
const paths = [
|
|
45
|
+
...new Set(graph.nodes
|
|
46
|
+
.map((n) => n.path)
|
|
47
|
+
.filter((p) => typeof p === "string" && /auth|login|session|password/i.test(p))),
|
|
48
|
+
].slice(0, 8);
|
|
49
|
+
if (paths.length) {
|
|
50
|
+
for (const p of paths) {
|
|
51
|
+
sections.push(` - ${p} (INFERRED from graph)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
sections.push(" UNKNOWN — no auth paths matched; scan src/**/auth* or routes manually.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
sections.push(" UNKNOWN — no auth paths matched; scan src/**/auth* or routes manually.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
sections.push("");
|
|
63
|
+
}
|
|
64
|
+
if (START_RE.test(q)) {
|
|
65
|
+
const startHits = context.citations
|
|
66
|
+
.filter((c) => c.path && /(server|index|main|app)\.[jt]sx?$/i.test(c.path))
|
|
67
|
+
.slice(0, 6);
|
|
68
|
+
sections.push("[INFERRED] Likely entry-related paths:");
|
|
69
|
+
if (startHits.length) {
|
|
70
|
+
for (const c of startHits) {
|
|
71
|
+
sections.push(` - ${c.path} (${c.confidence})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
try {
|
|
76
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
77
|
+
const paths = [
|
|
78
|
+
...new Set(graph.nodes
|
|
79
|
+
.map((n) => n.path)
|
|
80
|
+
.filter((p) => typeof p === "string" && /(server|index|main|app)\.[jt]sx?$/i.test(p))),
|
|
81
|
+
].slice(0, 6);
|
|
82
|
+
if (paths.length) {
|
|
83
|
+
for (const p of paths)
|
|
84
|
+
sections.push(` - ${p} (INFERRED from graph)`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
sections.push(" UNKNOWN — no clear entrypoint path in graph.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
sections.push(" UNKNOWN — no clear entrypoint path in graph.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
sections.push("");
|
|
95
|
+
}
|
|
96
|
+
if (DB_RE.test(q)) {
|
|
97
|
+
const dbHits = context.citations
|
|
98
|
+
.filter((c) => c.path && /(db|database|prisma|sql|migration)/i.test(c.path))
|
|
99
|
+
.slice(0, 6);
|
|
100
|
+
sections.push("[INFERRED] Database-related paths:");
|
|
101
|
+
if (dbHits.length) {
|
|
102
|
+
for (const c of dbHits)
|
|
103
|
+
sections.push(` - ${c.path} (${c.confidence})`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
try {
|
|
107
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
108
|
+
const paths = [
|
|
109
|
+
...new Set(graph.nodes
|
|
110
|
+
.map((n) => n.path)
|
|
111
|
+
.filter((p) => typeof p === "string" && /(db|database|prisma|sql)/i.test(p))),
|
|
112
|
+
].slice(0, 6);
|
|
113
|
+
if (paths.length) {
|
|
114
|
+
for (const p of paths)
|
|
115
|
+
sections.push(` - ${p} (INFERRED from graph)`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
sections.push(" UNKNOWN — no database path markers in graph.");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
sections.push(" UNKNOWN — no database path markers in graph.");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
sections.push("");
|
|
126
|
+
}
|
|
127
|
+
if (DEPS_RE.test(q)) {
|
|
128
|
+
const deps = await analyzeDependencies(root);
|
|
129
|
+
sections.push("[VERIFIED] Direct dependencies (sample):");
|
|
130
|
+
for (const d of deps.directDependencies.slice(0, 12)) {
|
|
131
|
+
sections.push(` - ${d.name} ${d.versionRange} (${d.packageJsonPath})`);
|
|
132
|
+
}
|
|
133
|
+
if (deps.transitiveFromLockfile?.length) {
|
|
134
|
+
sections.push("[VERIFIED] Transitive versions from lockfile (sample):");
|
|
135
|
+
for (const t of deps.transitiveFromLockfile.slice(0, 12)) {
|
|
136
|
+
sections.push(` - ${t.name}@${t.version}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
sections.push(`Lockfiles: ${deps.lockfilesPresent.join(", ") || "none"}`);
|
|
140
|
+
sections.push("");
|
|
141
|
+
}
|
|
142
|
+
if (ARCH_RE.test(q)) {
|
|
143
|
+
try {
|
|
144
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
145
|
+
sections.push(`[INFERRED] Graph: ${graph.nodes.length} nodes, ${graph.edges.length} edges (${graph.builder})`);
|
|
146
|
+
const sample = graph.nodes.filter((n) => n.path).slice(0, 6);
|
|
147
|
+
for (const n of sample) {
|
|
148
|
+
sections.push(` - ${n.kind}: ${n.label}${n.path ? ` @ ${n.path}` : ""}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
sections.push("[UNKNOWN] Intelligence graph build failed.");
|
|
153
|
+
}
|
|
154
|
+
sections.push("");
|
|
155
|
+
}
|
|
156
|
+
const brain = await getBrainStatus(root);
|
|
157
|
+
if (brain.hasSnapshot) {
|
|
158
|
+
sections.push(`[INFERRED] Project brain snapshot present (${brain.snapshotCount} stored; latest ${brain.latestSnapshotId ?? "unknown"}).`);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
sections.push("[UNKNOWN] No project brain snapshot — run brain compile for richer answers.");
|
|
162
|
+
}
|
|
163
|
+
sections.push("");
|
|
164
|
+
sections.push("Limitations: deterministic answers cannot invent behavior not present in evidence.");
|
|
165
|
+
const bundle = {
|
|
166
|
+
...context,
|
|
167
|
+
limitations: [
|
|
168
|
+
...context.limitations,
|
|
169
|
+
"Deterministic chat mode — no LLM; answers are template + local analyzers only.",
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
return buildChatTurnResponse({
|
|
173
|
+
sessionId: options.sessionId,
|
|
174
|
+
modelText: sections.join("\n"),
|
|
175
|
+
context: bundle,
|
|
176
|
+
provider: "deterministic",
|
|
177
|
+
model: "local-analyzers",
|
|
178
|
+
status: "ok",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { createModelProvider, loadAiConfig, publicAiConfig, redactForModel, } from "../../ai/index.js";
|
|
3
3
|
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
4
4
|
import { appendSessionEvent, createSession, endSession, } from "../../platform/sessions/store.js";
|
|
5
5
|
import { resolveRepoRoot } from "../../utils/path.js";
|
|
@@ -8,6 +8,7 @@ import { ChatMemory } from "./memory.js";
|
|
|
8
8
|
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
|
+
import { answerDeterministicProjectQuestion } from "./deterministic.js";
|
|
11
12
|
export const CHAT_PROVIDER_NONE_MESSAGE = `AI chat is not configured.
|
|
12
13
|
|
|
13
14
|
Configure an AI provider to use AgentDoctor Project Chat.
|
|
@@ -99,20 +100,13 @@ export class ChatService {
|
|
|
99
100
|
chars: String(safeUser.length),
|
|
100
101
|
});
|
|
101
102
|
if (this.provider.id === "none") {
|
|
102
|
-
const response = {
|
|
103
|
+
const response = await answerDeterministicProjectQuestion({
|
|
104
|
+
root: this.root,
|
|
105
|
+
question: safeUser,
|
|
103
106
|
sessionId: this.sessionId,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
provider: "none",
|
|
108
|
-
model: "none",
|
|
109
|
-
status: "provider-none",
|
|
110
|
-
error: AI_PROVIDER_REQUIRED_MESSAGE,
|
|
111
|
-
contextPaths: [],
|
|
112
|
-
contextTruncated: false,
|
|
113
|
-
limitations: [],
|
|
114
|
-
};
|
|
115
|
-
await this.audit("error", "CHAT_FAILED", { reason: "provider-none" });
|
|
107
|
+
});
|
|
108
|
+
this.memory.addAssistant(response.message, response.contextPaths);
|
|
109
|
+
await this.audit("prompt", "CHAT_DETERMINISTIC", { status: response.status });
|
|
116
110
|
return response;
|
|
117
111
|
}
|
|
118
112
|
const query = this.memory.resolveQuery(safeUser);
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { buildAgentPlan, formatAgentPlan, approvePlan } from "./plan.js";
|
|
|
18
18
|
export type { AgentPlan, AgentPlanStep } from "./plan.js";
|
|
19
19
|
export { runCodingLoop } from "./loop.js";
|
|
20
20
|
export type { CodingLoopOptions, CodingLoopResult } from "./loop.js";
|
|
21
|
+
export { rolePrompt, roleAllowedTools, runRoleAgent } from "./roles.js";
|
|
22
|
+
export type { AgentRole, RoleAgentOptions } from "./roles.js";
|
|
21
23
|
export { verifyAgentWork, formatVerificationReport } from "./verify.js";
|
|
22
24
|
export type { AgentVerificationReport, VerificationCheck } from "./verify.js";
|
|
23
25
|
export { getModeProfile, defaultStudentMode, parseAgentMode, modeAllowsMutation, modeBlocksToolCategory, } from "./modes.js";
|
package/dist/agent/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { listAgentToolSpecs, getToolSpec, riskForTool, executeAgentTool, isReadT
|
|
|
9
9
|
export { evaluateApproval, formatApprovalPrompt } from "./approvals.js";
|
|
10
10
|
export { buildAgentPlan, formatAgentPlan, approvePlan } from "./plan.js";
|
|
11
11
|
export { runCodingLoop } from "./loop.js";
|
|
12
|
+
export { rolePrompt, roleAllowedTools, runRoleAgent } from "./roles.js";
|
|
12
13
|
export { verifyAgentWork, formatVerificationReport } from "./verify.js";
|
|
13
14
|
export { getModeProfile, defaultStudentMode, parseAgentMode, modeAllowsMutation, modeBlocksToolCategory, } from "./modes.js";
|
|
14
15
|
export { StudentService } from "./student.js";
|
package/dist/agent/loop.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface CodingLoopOptions {
|
|
|
30
30
|
mode?: AgentMode;
|
|
31
31
|
/** Optional workspace isolation context */
|
|
32
32
|
workspace?: WorkspaceModel | null;
|
|
33
|
+
/** When set, only these tools may run (role agents / restricted turns) */
|
|
34
|
+
allowedTools?: AgentToolName[];
|
|
33
35
|
}
|
|
34
36
|
export interface CodingLoopResult {
|
|
35
37
|
state: AgentState;
|
package/dist/agent/loop.js
CHANGED
|
@@ -6,6 +6,7 @@ import { executeAgentTool, listAgentToolSpecs, newToolCall } from "./tools/index
|
|
|
6
6
|
import { getToolSpec } from "./tools/registry.js";
|
|
7
7
|
import { verifyAgentWork } from "./verify.js";
|
|
8
8
|
import { modeAllowsMutation } from "./modes.js";
|
|
9
|
+
import { appendChangeLedgerEntry } from "../product/ledger/change-ledger.js";
|
|
9
10
|
function toProviderTools(mode) {
|
|
10
11
|
const includeWrite = modeAllowsMutation(mode);
|
|
11
12
|
return listAgentToolSpecs({
|
|
@@ -88,6 +89,22 @@ export async function runCodingLoop(options) {
|
|
|
88
89
|
...(options.workspace !== undefined ? { workspace: options.workspace } : {}),
|
|
89
90
|
};
|
|
90
91
|
const runOne = async (call) => {
|
|
92
|
+
if (options.allowedTools && !options.allowedTools.includes(call.name)) {
|
|
93
|
+
const denied = {
|
|
94
|
+
callId: call.id,
|
|
95
|
+
name: call.name,
|
|
96
|
+
ok: false,
|
|
97
|
+
data: null,
|
|
98
|
+
risk: "LOW",
|
|
99
|
+
durationMs: 0,
|
|
100
|
+
error: {
|
|
101
|
+
code: "role_forbidden",
|
|
102
|
+
message: `Tool ${call.name} is not allowed for this role/session allowlist`,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
toolResults.push(denied);
|
|
106
|
+
return denied;
|
|
107
|
+
}
|
|
91
108
|
const spec = getToolSpec(call.name);
|
|
92
109
|
const pendingWrite = spec?.category === "write";
|
|
93
110
|
toolCalls += 1;
|
|
@@ -191,6 +208,20 @@ export async function runCodingLoop(options) {
|
|
|
191
208
|
toolResults.push(status);
|
|
192
209
|
}
|
|
193
210
|
machine.transition(AgentState.COMPLETED, "coding loop done");
|
|
211
|
+
if (filesChanged.length > 0) {
|
|
212
|
+
try {
|
|
213
|
+
await appendChangeLedgerEntry(options.root, {
|
|
214
|
+
task: options.goal,
|
|
215
|
+
plan: plan.goal,
|
|
216
|
+
approval: "approvedByHuman",
|
|
217
|
+
files: [...new Set(filesChanged)],
|
|
218
|
+
note: "coding-loop:completed",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// best-effort — never fail the loop on ledger persistence
|
|
223
|
+
}
|
|
224
|
+
}
|
|
194
225
|
const text = verification
|
|
195
226
|
? verification.summaryText
|
|
196
227
|
: [
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentToolName } from "./tools/types.js";
|
|
2
|
+
import { type CodingLoopOptions, type CodingLoopResult } from "./loop.js";
|
|
3
|
+
export type AgentRole = "planner" | "coder" | "tester" | "reviewer" | "security" | "refactoring" | "migration" | "documentation" | "release" | "verifier";
|
|
4
|
+
export declare function rolePrompt(role: AgentRole): string;
|
|
5
|
+
export declare function roleAllowedTools(role: AgentRole): AgentToolName[];
|
|
6
|
+
export type RoleAgentOptions = CodingLoopOptions & {
|
|
7
|
+
role: AgentRole;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Same coding loop as the main agent, with a role-specific system note prepended to the goal.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runRoleAgent(options: RoleAgentOptions): Promise<CodingLoopResult>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { listAgentToolSpecs } from "./tools/registry.js";
|
|
2
|
+
import { runCodingLoop } from "./loop.js";
|
|
3
|
+
const ALL_TOOL_NAMES = listAgentToolSpecs({
|
|
4
|
+
includeWrite: true,
|
|
5
|
+
includeExecute: true,
|
|
6
|
+
}).map((t) => t.name);
|
|
7
|
+
const ROLE_TOOL_ALLOWLIST = {
|
|
8
|
+
planner: [
|
|
9
|
+
"read_file",
|
|
10
|
+
"list_files",
|
|
11
|
+
"search_code",
|
|
12
|
+
"find_symbol",
|
|
13
|
+
"inspect_project",
|
|
14
|
+
"inspect_architecture",
|
|
15
|
+
"inspect_dependencies",
|
|
16
|
+
"inspect_git_status",
|
|
17
|
+
"inspect_git_diff",
|
|
18
|
+
],
|
|
19
|
+
coder: ALL_TOOL_NAMES,
|
|
20
|
+
tester: [
|
|
21
|
+
"read_file",
|
|
22
|
+
"list_files",
|
|
23
|
+
"search_code",
|
|
24
|
+
"find_symbol",
|
|
25
|
+
"inspect_project",
|
|
26
|
+
"inspect_tests",
|
|
27
|
+
"run_tests",
|
|
28
|
+
"run_command",
|
|
29
|
+
"inspect_git_diff",
|
|
30
|
+
],
|
|
31
|
+
reviewer: [
|
|
32
|
+
"read_file",
|
|
33
|
+
"list_files",
|
|
34
|
+
"search_code",
|
|
35
|
+
"find_symbol",
|
|
36
|
+
"find_references",
|
|
37
|
+
"find_callers",
|
|
38
|
+
"find_callees",
|
|
39
|
+
"inspect_project",
|
|
40
|
+
"inspect_architecture",
|
|
41
|
+
"inspect_findings",
|
|
42
|
+
"inspect_git_diff",
|
|
43
|
+
],
|
|
44
|
+
security: [
|
|
45
|
+
"read_file",
|
|
46
|
+
"list_files",
|
|
47
|
+
"search_code",
|
|
48
|
+
"inspect_project",
|
|
49
|
+
"inspect_findings",
|
|
50
|
+
"inspect_git_diff",
|
|
51
|
+
],
|
|
52
|
+
refactoring: [
|
|
53
|
+
"read_file",
|
|
54
|
+
"list_files",
|
|
55
|
+
"search_code",
|
|
56
|
+
"find_symbol",
|
|
57
|
+
"find_references",
|
|
58
|
+
"find_callers",
|
|
59
|
+
"find_callees",
|
|
60
|
+
"inspect_dependencies",
|
|
61
|
+
"edit_file",
|
|
62
|
+
"create_file",
|
|
63
|
+
],
|
|
64
|
+
migration: [
|
|
65
|
+
"read_file",
|
|
66
|
+
"list_files",
|
|
67
|
+
"search_code",
|
|
68
|
+
"find_symbol",
|
|
69
|
+
"inspect_project",
|
|
70
|
+
"create_file",
|
|
71
|
+
"edit_file",
|
|
72
|
+
"run_command",
|
|
73
|
+
"run_tests",
|
|
74
|
+
],
|
|
75
|
+
documentation: [
|
|
76
|
+
"read_file",
|
|
77
|
+
"list_files",
|
|
78
|
+
"search_code",
|
|
79
|
+
"inspect_project",
|
|
80
|
+
"create_file",
|
|
81
|
+
"edit_file",
|
|
82
|
+
],
|
|
83
|
+
release: [
|
|
84
|
+
"read_file",
|
|
85
|
+
"list_files",
|
|
86
|
+
"inspect_project",
|
|
87
|
+
"inspect_git_status",
|
|
88
|
+
"inspect_git_diff",
|
|
89
|
+
"run_command",
|
|
90
|
+
"run_tests",
|
|
91
|
+
],
|
|
92
|
+
verifier: [
|
|
93
|
+
"read_file",
|
|
94
|
+
"list_files",
|
|
95
|
+
"inspect_tests",
|
|
96
|
+
"inspect_findings",
|
|
97
|
+
"run_tests",
|
|
98
|
+
"run_command",
|
|
99
|
+
"inspect_git_diff",
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
const ROLE_PROMPTS = {
|
|
103
|
+
planner: "Role: planner. Produce a concise, evidence-backed plan. Prefer read-only inspection; do not mutate files unless explicitly approved.",
|
|
104
|
+
coder: "Role: coder. Implement the goal with minimal, focused diffs. Respect approval gates for writes and command execution.",
|
|
105
|
+
tester: "Role: tester. Focus on test coverage, failing cases, and controlled test runs. Avoid unrelated refactors.",
|
|
106
|
+
reviewer: "Role: reviewer. Critique changes for correctness, regressions, and architecture fit. Stay read-only.",
|
|
107
|
+
security: "Role: security. Hunt for secrets, unsafe patterns, and auth gaps. Never exfiltrate or log secret values.",
|
|
108
|
+
refactoring: "Role: refactoring. Improve structure without behavior changes; use reference/call tools before edits.",
|
|
109
|
+
migration: "Role: migration. Coordinate mechanical moves/upgrades with verification after each batch.",
|
|
110
|
+
documentation: "Role: documentation. Update docs and comments for accuracy; match project tone.",
|
|
111
|
+
release: "Role: release. Prepare release checks (git status, tests, changelog hints); avoid drive-by changes.",
|
|
112
|
+
verifier: "Role: verifier. Re-run tests and scans to confirm the goal is met; report evidence clearly.",
|
|
113
|
+
};
|
|
114
|
+
export function rolePrompt(role) {
|
|
115
|
+
return ROLE_PROMPTS[role];
|
|
116
|
+
}
|
|
117
|
+
export function roleAllowedTools(role) {
|
|
118
|
+
return [...ROLE_TOOL_ALLOWLIST[role]];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Same coding loop as the main agent, with a role-specific system note prepended to the goal.
|
|
122
|
+
*/
|
|
123
|
+
export async function runRoleAgent(options) {
|
|
124
|
+
const allowed = new Set(roleAllowedTools(options.role));
|
|
125
|
+
const roleNote = [
|
|
126
|
+
rolePrompt(options.role),
|
|
127
|
+
`Allowed tools for this role: ${[...allowed].join(", ")}.`,
|
|
128
|
+
"If a tool is outside the role allowlist, explain the limitation instead of attempting it.",
|
|
129
|
+
].join("\n");
|
|
130
|
+
const goal = `${roleNote}\n\nUser goal:\n${options.goal}`;
|
|
131
|
+
const filteredToolCalls = options.toolCalls?.filter((tc) => allowed.has(tc.name));
|
|
132
|
+
return runCodingLoop({
|
|
133
|
+
...options,
|
|
134
|
+
goal,
|
|
135
|
+
allowedTools: [...allowed],
|
|
136
|
+
...(filteredToolCalls !== undefined ? { toolCalls: filteredToolCalls } : {}),
|
|
137
|
+
});
|
|
138
|
+
}
|
package/dist/agent/runtime.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ModelProvider } from "../ai/index.js";
|
|
2
2
|
import { AgentState, AgentStateMachine, type AgentStateTransition } from "./state.js";
|
|
3
3
|
import type { ContextBundle } from "./context/types.js";
|
|
4
|
+
import type { AgentToolName } from "./tools/types.js";
|
|
5
|
+
import { type AgentMode } from "./modes.js";
|
|
4
6
|
export interface AgentLimits {
|
|
5
7
|
maxToolCalls: number;
|
|
6
8
|
maxIterations: number;
|
|
@@ -9,7 +11,7 @@ export interface AgentLimits {
|
|
|
9
11
|
maxContextChars: number;
|
|
10
12
|
}
|
|
11
13
|
export declare const DEFAULT_AGENT_LIMITS: AgentLimits;
|
|
12
|
-
export type AgentAuditEventType = "session-start" | "state-transition" | "context-retrieved" | "model-call" | "model-error" | "limit-exceeded" | "session-end";
|
|
14
|
+
export type AgentAuditEventType = "session-start" | "state-transition" | "context-retrieved" | "model-call" | "model-error" | "tool-call" | "tool-result" | "limit-exceeded" | "session-end";
|
|
13
15
|
export interface AgentAuditEvent {
|
|
14
16
|
id: string;
|
|
15
17
|
sessionId: string;
|
|
@@ -32,10 +34,16 @@ export interface AgentTurnResult {
|
|
|
32
34
|
transitions: readonly AgentStateTransition[];
|
|
33
35
|
audit: readonly AgentAuditEvent[];
|
|
34
36
|
context?: ContextBundle;
|
|
37
|
+
toolResults?: Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
ok: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
}>;
|
|
42
|
+
filesChanged?: string[];
|
|
35
43
|
}
|
|
36
44
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
45
|
+
* Agent runtime: state machine + provider chat + optional tool execution.
|
|
46
|
+
* Shares the same tool execution + approval gates as runCodingLoop.
|
|
39
47
|
*/
|
|
40
48
|
export declare class AgentRuntime {
|
|
41
49
|
readonly sessionId: string;
|
|
@@ -48,6 +56,7 @@ export declare class AgentRuntime {
|
|
|
48
56
|
private readonly startedAt;
|
|
49
57
|
private toolCalls;
|
|
50
58
|
private iterations;
|
|
59
|
+
private filesModified;
|
|
51
60
|
private cancelled;
|
|
52
61
|
constructor(options: AgentRuntimeOptions);
|
|
53
62
|
get events(): readonly AgentAuditEvent[];
|
|
@@ -55,13 +64,20 @@ export declare class AgentRuntime {
|
|
|
55
64
|
private emit;
|
|
56
65
|
private transition;
|
|
57
66
|
private assertWithinLimits;
|
|
67
|
+
private providerTools;
|
|
58
68
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
69
|
+
* Full turn: UNDERSTANDING → model chat → optional tool execution loop → VERIFYING → COMPLETED.
|
|
70
|
+
* Tool writes/executes require approvedByHuman=true (same gate as runCodingLoop).
|
|
61
71
|
*/
|
|
62
72
|
runTurn(options: {
|
|
63
73
|
userMessage: string;
|
|
64
74
|
systemPrompt?: string;
|
|
65
75
|
context?: ContextBundle;
|
|
76
|
+
/** Execute model-proposed tools (default true when tools present) */
|
|
77
|
+
executeTools?: boolean;
|
|
78
|
+
/** Required for write/execute tools */
|
|
79
|
+
approvedByHuman?: boolean;
|
|
80
|
+
mode?: AgentMode;
|
|
81
|
+
allowedTools?: AgentToolName[];
|
|
66
82
|
}): Promise<AgentTurnResult>;
|
|
67
83
|
}
|