@praneeth_54/agentdoctor 2.1.0 → 3.0.1

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.
Files changed (167) hide show
  1. package/CHANGELOG.md +86 -3
  2. package/README.md +490 -290
  3. package/dist/agent/chat/deterministic.d.ts +6 -0
  4. package/dist/agent/chat/deterministic.js +180 -0
  5. package/dist/agent/chat/service.js +8 -14
  6. package/dist/agent/context/retrieve.d.ts +2 -1
  7. package/dist/agent/context/retrieve.js +14 -1
  8. package/dist/agent/index.d.ts +2 -0
  9. package/dist/agent/index.js +1 -0
  10. package/dist/agent/loop.d.ts +2 -0
  11. package/dist/agent/loop.js +31 -0
  12. package/dist/agent/roles.d.ts +12 -0
  13. package/dist/agent/roles.js +138 -0
  14. package/dist/agent/runtime.d.ts +21 -5
  15. package/dist/agent/runtime.js +141 -28
  16. package/dist/agent/tools/execute.d.ts +2 -0
  17. package/dist/agent/tools/execute.js +49 -43
  18. package/dist/agent/tools/write.d.ts +1 -1
  19. package/dist/agent/tools/write.js +18 -4
  20. package/dist/ai/config.js +1 -0
  21. package/dist/ai/providers/adversarial-local.d.ts +7 -0
  22. package/dist/ai/providers/adversarial-local.js +146 -0
  23. package/dist/ai/redact.d.ts +1 -0
  24. package/dist/ai/redact.js +47 -7
  25. package/dist/ai/types.d.ts +1 -1
  26. package/dist/cli/commands/agent.d.ts +2 -0
  27. package/dist/cli/commands/agent.js +32 -11
  28. package/dist/cli/commands/architecture.js +7 -2
  29. package/dist/cli/commands/brain.js +7 -2
  30. package/dist/cli/commands/chat.js +15 -7
  31. package/dist/cli/commands/complete.js +13 -3
  32. package/dist/cli/commands/fix.js +6 -0
  33. package/dist/cli/commands/learn.js +7 -2
  34. package/dist/cli/commands/mcp.js +7 -3
  35. package/dist/cli/commands/platform.js +7 -2
  36. package/dist/cli/commands/policy-graph-run.js +7 -0
  37. package/dist/cli/commands/product.d.ts +19 -0
  38. package/dist/cli/commands/product.js +157 -0
  39. package/dist/cli/commands/scan.js +6 -0
  40. package/dist/cli/commands/start.d.ts +15 -0
  41. package/dist/cli/commands/start.js +86 -0
  42. package/dist/cli/commands/v2.js +61 -11
  43. package/dist/cli/commands/verify.js +6 -0
  44. package/dist/cli/program.js +141 -0
  45. package/dist/cli/safe-root.d.ts +17 -0
  46. package/dist/cli/safe-root.js +32 -0
  47. package/dist/constants.d.ts +1 -1
  48. package/dist/constants.js +1 -1
  49. package/dist/core/brain-cli/service.js +5 -2
  50. package/dist/core/monorepo/detect.js +10 -0
  51. package/dist/core/secrets/scan.js +9 -0
  52. package/dist/core/understanding/brain/storage/store.d.ts +4 -0
  53. package/dist/core/understanding/brain/storage/store.js +22 -2
  54. package/dist/dashboard/page.d.ts +2 -0
  55. package/dist/dashboard/page.js +53 -0
  56. package/dist/dashboard/server.js +117 -140
  57. package/dist/dashboard/ui/client.d.ts +2 -0
  58. package/dist/dashboard/ui/client.js +2 -0
  59. package/dist/dashboard/ui/styles.d.ts +2 -0
  60. package/dist/dashboard/ui/styles.js +138 -0
  61. package/dist/discovery/files.js +13 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +2 -0
  64. package/dist/intelligence/graph/build.js +46 -38
  65. package/dist/intelligence/graph/incremental.d.ts +2 -0
  66. package/dist/intelligence/graph/incremental.js +9 -0
  67. package/dist/intelligence/resolve/imports.js +1 -1
  68. package/dist/languages/dart.d.ts +10 -0
  69. package/dist/languages/dart.js +99 -0
  70. package/dist/languages/go.d.ts +13 -7
  71. package/dist/languages/go.js +84 -24
  72. package/dist/languages/index.d.ts +5 -1
  73. package/dist/languages/index.js +13 -36
  74. package/dist/languages/java.d.ts +10 -0
  75. package/dist/languages/java.js +80 -0
  76. package/dist/languages/kotlin.d.ts +10 -0
  77. package/dist/languages/kotlin.js +85 -0
  78. package/dist/languages/rust.d.ts +10 -0
  79. package/dist/languages/rust.js +93 -0
  80. package/dist/languages/types.d.ts +4 -3
  81. package/dist/mcp/agent/registry.d.ts +1 -1
  82. package/dist/mcp/agent/registry.js +109 -23
  83. package/dist/mcp/intelligence/handlers.d.ts +3 -0
  84. package/dist/mcp/intelligence/handlers.js +28 -0
  85. package/dist/mcp/intelligence/path-safety.d.ts +6 -1
  86. package/dist/mcp/intelligence/path-safety.js +55 -2
  87. package/dist/mcp/intelligence/registry.d.ts +1 -1
  88. package/dist/mcp/intelligence/registry.js +30 -1
  89. package/dist/platform/graph/build.js +9 -0
  90. package/dist/product/api/doctor.d.ts +14 -0
  91. package/dist/product/api/doctor.js +185 -0
  92. package/dist/product/api/openapi.d.ts +7 -0
  93. package/dist/product/api/openapi.js +122 -0
  94. package/dist/product/approval/model.d.ts +30 -0
  95. package/dist/product/approval/model.js +64 -0
  96. package/dist/product/approval/session.d.ts +49 -0
  97. package/dist/product/approval/session.js +134 -0
  98. package/dist/product/database/doctor.d.ts +30 -0
  99. package/dist/product/database/doctor.js +184 -0
  100. package/dist/product/decisions/ledger.d.ts +36 -0
  101. package/dist/product/decisions/ledger.js +141 -0
  102. package/dist/product/deps/analyze.d.ts +46 -0
  103. package/dist/product/deps/analyze.js +137 -0
  104. package/dist/product/deps/lockfiles.d.ts +25 -0
  105. package/dist/product/deps/lockfiles.js +200 -0
  106. package/dist/product/discovery/roots.d.ts +52 -0
  107. package/dist/product/discovery/roots.js +231 -0
  108. package/dist/product/dna/build.d.ts +50 -0
  109. package/dist/product/dna/build.js +255 -0
  110. package/dist/product/eval/lab.d.ts +17 -0
  111. package/dist/product/eval/lab.js +218 -0
  112. package/dist/product/events/doctor.d.ts +21 -0
  113. package/dist/product/events/doctor.js +147 -0
  114. package/dist/product/evidence-scan.d.ts +12 -0
  115. package/dist/product/evidence-scan.js +46 -0
  116. package/dist/product/evolution/timeline.d.ts +29 -0
  117. package/dist/product/evolution/timeline.js +123 -0
  118. package/dist/product/features/intelligence.d.ts +23 -0
  119. package/dist/product/features/intelligence.js +158 -0
  120. package/dist/product/forensic/mode.d.ts +25 -0
  121. package/dist/product/forensic/mode.js +70 -0
  122. package/dist/product/graph/enrich-languages.d.ts +20 -0
  123. package/dist/product/graph/enrich-languages.js +168 -0
  124. package/dist/product/health/code-health.d.ts +20 -0
  125. package/dist/product/health/code-health.js +149 -0
  126. package/dist/product/index.d.ts +72 -0
  127. package/dist/product/index.js +36 -0
  128. package/dist/product/ledger/change-ledger.d.ts +23 -0
  129. package/dist/product/ledger/change-ledger.js +64 -0
  130. package/dist/product/map/software-map.d.ts +17 -0
  131. package/dist/product/map/software-map.js +96 -0
  132. package/dist/product/memory/institutional.d.ts +16 -0
  133. package/dist/product/memory/institutional.js +92 -0
  134. package/dist/product/ops/incident.d.ts +20 -0
  135. package/dist/product/ops/incident.js +61 -0
  136. package/dist/product/ops/infra.d.ts +15 -0
  137. package/dist/product/ops/infra.js +112 -0
  138. package/dist/product/org/model.d.ts +34 -0
  139. package/dist/product/org/model.js +195 -0
  140. package/dist/product/privacy/doctor.d.ts +13 -0
  141. package/dist/product/privacy/doctor.js +90 -0
  142. package/dist/product/requirements/trace.d.ts +21 -0
  143. package/dist/product/requirements/trace.js +166 -0
  144. package/dist/product/search/index.d.ts +29 -0
  145. package/dist/product/search/index.js +116 -0
  146. package/dist/product/search/software-search.d.ts +19 -0
  147. package/dist/product/search/software-search.js +105 -0
  148. package/dist/product/security/doctor.d.ts +23 -0
  149. package/dist/product/security/doctor.js +124 -0
  150. package/dist/product/self/diagnose.d.ts +15 -0
  151. package/dist/product/self/diagnose.js +82 -0
  152. package/dist/product/techdebt/roadmap.d.ts +20 -0
  153. package/dist/product/techdebt/roadmap.js +118 -0
  154. package/dist/product/testbrain/analyze.d.ts +29 -0
  155. package/dist/product/testbrain/analyze.js +129 -0
  156. package/dist/product/truth.d.ts +12 -0
  157. package/dist/product/truth.js +30 -0
  158. package/dist/product/twin/digital-twin.d.ts +23 -0
  159. package/dist/product/twin/digital-twin.js +52 -0
  160. package/dist/product/twin/store.d.ts +21 -0
  161. package/dist/product/twin/store.js +78 -0
  162. package/dist/product/whatif/engine.d.ts +28 -0
  163. package/dist/product/whatif/engine.js +91 -0
  164. package/dist/project/ownership.d.ts +65 -0
  165. package/dist/project/ownership.js +169 -0
  166. package/dist/utils/fs.js +10 -2
  167. package/package.json +1 -1
@@ -0,0 +1,6 @@
1
+ import type { ChatTurnResponse } from "./types.js";
2
+ export declare function answerDeterministicProjectQuestion(options: {
3
+ root: string;
4
+ question: string;
5
+ sessionId: string;
6
+ }): Promise<ChatTurnResponse>;
@@ -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 { AI_PROVIDER_REQUIRED_MESSAGE, createModelProvider, loadAiConfig, publicAiConfig, redactForModel, } from "../../ai/index.js";
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
- message: CHAT_PROVIDER_NONE_MESSAGE,
105
- truthClaims: [],
106
- citations: [],
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);
@@ -16,6 +16,7 @@ export interface RetrieveContextOptions {
16
16
  /**
17
17
  * Retrieve a budgeted project context pack for the agent.
18
18
  * Reuses graph + planContext. Does not invent facts.
19
- * Hostile paths are rejected via resolveSafeRepoPath.
19
+ * Hostile paths are rejected via resolveSafeRepoPath + project ownership
20
+ * (containment alone must not promote .private / AgentDoctorOS / nested repos).
20
21
  */
21
22
  export declare function retrieveProjectContext(options: RetrieveContextOptions): Promise<ContextBundle>;
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { planContext } from "../../platform/tokens/plan.js";
4
4
  import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
5
+ import { assertProjectOwnedRepoPath, ProjectOwnershipError } from "../../project/ownership.js";
5
6
  import { resolveRepoRoot } from "../../utils/path.js";
6
7
  import { resolveSafeRepoPath, PathEscapeError } from "../../security/paths.js";
7
8
  function estimateTokens(text) {
@@ -10,7 +11,8 @@ function estimateTokens(text) {
10
11
  /**
11
12
  * Retrieve a budgeted project context pack for the agent.
12
13
  * Reuses graph + planContext. Does not invent facts.
13
- * Hostile paths are rejected via resolveSafeRepoPath.
14
+ * Hostile paths are rejected via resolveSafeRepoPath + project ownership
15
+ * (containment alone must not promote .private / AgentDoctorOS / nested repos).
14
16
  */
15
17
  export async function retrieveProjectContext(options) {
16
18
  const root = resolveRepoRoot(options.root);
@@ -57,6 +59,7 @@ export async function retrieveProjectContext(options) {
57
59
  for (const rel of paths) {
58
60
  try {
59
61
  const abs = resolveSafeRepoPath(root, rel);
62
+ await assertProjectOwnedRepoPath(root, abs, rel);
60
63
  const text = await fs.readFile(abs, "utf8");
61
64
  const excerpt = text.slice(0, maxExcerpt);
62
65
  const citation = {
@@ -80,6 +83,16 @@ export async function retrieveProjectContext(options) {
80
83
  });
81
84
  continue;
82
85
  }
86
+ if (error instanceof ProjectOwnershipError) {
87
+ citations.push({
88
+ source: "repository",
89
+ path: rel,
90
+ evidenceType: "metadata",
91
+ confidence: "UNKNOWN",
92
+ note: `ownership_denied: ${error.ownership}`,
93
+ });
94
+ continue;
95
+ }
83
96
  citations.push({
84
97
  source: "repository",
85
98
  path: rel,
@@ -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";
@@ -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";
@@ -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;
@@ -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
+ }
@@ -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
- * Minimal agent runtime for M1: state machine + provider chat + limits.
38
- * Tool execution and approvals arrive in later milestones.
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
- * M1 turn: UNDERSTANDING → (optional context) → model chat → COMPLETED/FAILED.
60
- * Does not execute tools yet.
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
  }