@praneeth_54/agentdoctor 1.1.1 → 2.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 (224) hide show
  1. package/CHANGELOG.md +164 -7
  2. package/README.md +263 -513
  3. package/dist/agents/aider/adapter.d.ts +1 -0
  4. package/dist/agents/aider/adapter.js +1 -0
  5. package/dist/agents/aider/detector.d.ts +14 -0
  6. package/dist/agents/aider/detector.js +120 -0
  7. package/dist/agents/copilot/adapter.d.ts +1 -0
  8. package/dist/agents/copilot/adapter.js +1 -0
  9. package/dist/agents/copilot/detector.d.ts +13 -0
  10. package/dist/agents/copilot/detector.js +113 -0
  11. package/dist/agents/gemini/adapter.d.ts +1 -0
  12. package/dist/agents/gemini/adapter.js +1 -0
  13. package/dist/agents/gemini/detector.d.ts +13 -0
  14. package/dist/agents/gemini/detector.js +151 -0
  15. package/dist/agents/registry.js +13 -1
  16. package/dist/agents/types.d.ts +1 -1
  17. package/dist/agents/windsurf/adapter.d.ts +1 -0
  18. package/dist/agents/windsurf/adapter.js +1 -0
  19. package/dist/agents/windsurf/detector.d.ts +14 -0
  20. package/dist/agents/windsurf/detector.js +119 -0
  21. package/dist/architecture/c4.d.ts +22 -0
  22. package/dist/architecture/c4.js +62 -0
  23. package/dist/architecture/contract.d.ts +64 -0
  24. package/dist/architecture/contract.js +373 -0
  25. package/dist/assurance/change.d.ts +211 -0
  26. package/dist/assurance/change.js +624 -0
  27. package/dist/assurance/proof.d.ts +136 -0
  28. package/dist/assurance/proof.js +338 -0
  29. package/dist/auth/index.d.ts +81 -0
  30. package/dist/auth/index.js +130 -0
  31. package/dist/auth/rbac.d.ts +9 -0
  32. package/dist/auth/rbac.js +26 -0
  33. package/dist/cli/commands/architecture.d.ts +6 -0
  34. package/dist/cli/commands/architecture.js +109 -0
  35. package/dist/cli/commands/assurance.d.ts +67 -0
  36. package/dist/cli/commands/assurance.js +210 -0
  37. package/dist/cli/commands/brain.d.ts +12 -0
  38. package/dist/cli/commands/brain.js +162 -0
  39. package/dist/cli/commands/complete.d.ts +23 -0
  40. package/dist/cli/commands/complete.js +232 -0
  41. package/dist/cli/commands/explain.js +1 -1
  42. package/dist/cli/commands/fix.js +22 -2
  43. package/dist/cli/commands/mcp.d.ts +8 -0
  44. package/dist/cli/commands/mcp.js +14 -0
  45. package/dist/cli/commands/platform.d.ts +17 -0
  46. package/dist/cli/commands/platform.js +195 -0
  47. package/dist/cli/commands/policy-graph-run.d.ts +40 -0
  48. package/dist/cli/commands/policy-graph-run.js +200 -0
  49. package/dist/cli/commands/v2.d.ts +55 -0
  50. package/dist/cli/commands/v2.js +278 -0
  51. package/dist/cli/commands/workspace.d.ts +9 -0
  52. package/dist/cli/commands/workspace.js +82 -0
  53. package/dist/cli/program.js +1328 -3
  54. package/dist/constants.d.ts +3 -2
  55. package/dist/constants.js +5 -1
  56. package/dist/contracts/adapters.d.ts +7 -0
  57. package/dist/contracts/adapters.js +60 -0
  58. package/dist/contracts/index.d.ts +162 -0
  59. package/dist/contracts/index.js +18 -0
  60. package/dist/core/baseline/store.d.ts +48 -0
  61. package/dist/core/baseline/store.js +163 -0
  62. package/dist/core/brain-cli/service.d.ts +26 -0
  63. package/dist/core/brain-cli/service.js +175 -0
  64. package/dist/core/brain-product/init.d.ts +45 -0
  65. package/dist/core/brain-product/init.js +235 -0
  66. package/dist/core/changes/analyze.d.ts +41 -0
  67. package/dist/core/changes/analyze.js +223 -0
  68. package/dist/core/context-health/analyze.d.ts +15 -0
  69. package/dist/core/context-health/analyze.js +234 -0
  70. package/dist/core/fix/apply.d.ts +21 -4
  71. package/dist/core/fix/apply.js +118 -18
  72. package/dist/core/fix/backup.d.ts +30 -0
  73. package/dist/core/fix/backup.js +193 -0
  74. package/dist/core/fix/plan.d.ts +6 -3
  75. package/dist/core/fix/plan.js +113 -9
  76. package/dist/core/fix/render.d.ts +2 -0
  77. package/dist/core/fix/render.js +36 -21
  78. package/dist/core/fix/run.js +5 -1
  79. package/dist/core/fix/safe-target.d.ts +14 -0
  80. package/dist/core/fix/safe-target.js +81 -0
  81. package/dist/core/fix/types.d.ts +12 -1
  82. package/dist/core/fix/types.js +2 -0
  83. package/dist/core/fix/writers/claude-settings.js +3 -1
  84. package/dist/core/fix/writers/codex-config.d.ts +3 -0
  85. package/dist/core/fix/writers/codex-config.js +24 -3
  86. package/dist/core/fix/writers/cursorignore.js +3 -1
  87. package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
  88. package/dist/core/fix/writers/simple-ignore.js +68 -0
  89. package/dist/core/monorepo/detect.d.ts +13 -0
  90. package/dist/core/monorepo/detect.js +121 -0
  91. package/dist/core/rules/build-context.js +22 -3
  92. package/dist/core/rules/context/generated-directory.js +11 -1
  93. package/dist/core/rules/context/large-instruction-file.js +6 -0
  94. package/dist/core/rules/context/large-log-file.js +16 -1
  95. package/dist/core/rules/ignore.d.ts +11 -1
  96. package/dist/core/rules/ignore.js +22 -1
  97. package/dist/core/rules/instructions/empty-instructions.js +7 -0
  98. package/dist/core/rules/security/env-file-exposure.js +23 -2
  99. package/dist/core/rules/security/private-key-file.js +1 -1
  100. package/dist/core/scanner/scan.js +1 -1
  101. package/dist/core/schemas/validate.d.ts +6 -0
  102. package/dist/core/schemas/validate.js +62 -0
  103. package/dist/core/scoring/compute-scores.d.ts +1 -1
  104. package/dist/core/scoring/compute-scores.js +5 -1
  105. package/dist/core/scoring/placeholder.d.ts +1 -1
  106. package/dist/core/scoring/placeholder.js +5 -1
  107. package/dist/core/secrets/scan.d.ts +29 -0
  108. package/dist/core/secrets/scan.js +172 -0
  109. package/dist/coverage/cobertura.d.ts +6 -0
  110. package/dist/coverage/cobertura.js +52 -0
  111. package/dist/coverage/istanbul.d.ts +7 -0
  112. package/dist/coverage/istanbul.js +134 -0
  113. package/dist/coverage/lcov.d.ts +7 -0
  114. package/dist/coverage/lcov.js +59 -0
  115. package/dist/coverage/load.d.ts +9 -0
  116. package/dist/coverage/load.js +39 -0
  117. package/dist/coverage/types.d.ts +23 -0
  118. package/dist/coverage/types.js +5 -0
  119. package/dist/dashboard/server.d.ts +24 -0
  120. package/dist/dashboard/server.js +402 -0
  121. package/dist/enforcement/runner.d.ts +67 -0
  122. package/dist/enforcement/runner.js +460 -0
  123. package/dist/index.d.ts +34 -0
  124. package/dist/index.js +25 -0
  125. package/dist/integrations/github/pr-analyze.d.ts +28 -0
  126. package/dist/integrations/github/pr-analyze.js +98 -0
  127. package/dist/integrations/local-ai/provider.d.ts +45 -0
  128. package/dist/integrations/local-ai/provider.js +104 -0
  129. package/dist/intelligence/git/analyze.d.ts +34 -0
  130. package/dist/intelligence/git/analyze.js +136 -0
  131. package/dist/intelligence/graph/build.d.ts +15 -0
  132. package/dist/intelligence/graph/build.js +259 -0
  133. package/dist/intelligence/graph/incremental.d.ts +68 -0
  134. package/dist/intelligence/graph/incremental.js +234 -0
  135. package/dist/intelligence/resolve/imports.d.ts +36 -0
  136. package/dist/intelligence/resolve/imports.js +245 -0
  137. package/dist/knowledge/store.d.ts +25 -0
  138. package/dist/knowledge/store.js +91 -0
  139. package/dist/languages/go.d.ts +9 -0
  140. package/dist/languages/go.js +45 -0
  141. package/dist/languages/index.d.ts +11 -0
  142. package/dist/languages/index.js +76 -0
  143. package/dist/languages/php.d.ts +7 -0
  144. package/dist/languages/php.js +193 -0
  145. package/dist/languages/python.d.ts +7 -0
  146. package/dist/languages/python.js +150 -0
  147. package/dist/languages/types.d.ts +42 -0
  148. package/dist/languages/types.js +26 -0
  149. package/dist/languages/typescript.d.ts +3 -0
  150. package/dist/languages/typescript.js +95 -0
  151. package/dist/mcp/agentdoctor/server.d.ts +16 -0
  152. package/dist/mcp/agentdoctor/server.js +67 -0
  153. package/dist/mcp/brain/session.js +7 -7
  154. package/dist/mcp/intelligence/handlers.d.ts +16 -0
  155. package/dist/mcp/intelligence/handlers.js +364 -0
  156. package/dist/mcp/intelligence/path-safety.d.ts +9 -0
  157. package/dist/mcp/intelligence/path-safety.js +63 -0
  158. package/dist/mcp/intelligence/registry.d.ts +8 -0
  159. package/dist/mcp/intelligence/registry.js +228 -0
  160. package/dist/ops/health.d.ts +18 -0
  161. package/dist/ops/health.js +51 -0
  162. package/dist/platform/ai-quality/analyze.d.ts +9 -0
  163. package/dist/platform/ai-quality/analyze.js +65 -0
  164. package/dist/platform/architecture/drift.d.ts +17 -0
  165. package/dist/platform/architecture/drift.js +74 -0
  166. package/dist/platform/auth/local.d.ts +15 -0
  167. package/dist/platform/auth/local.js +58 -0
  168. package/dist/platform/context-security/analyze.d.ts +6 -0
  169. package/dist/platform/context-security/analyze.js +97 -0
  170. package/dist/platform/firewall/evaluate.d.ts +57 -0
  171. package/dist/platform/firewall/evaluate.js +359 -0
  172. package/dist/platform/graph/build.d.ts +6 -0
  173. package/dist/platform/graph/build.js +189 -0
  174. package/dist/platform/health/analyze.d.ts +7 -0
  175. package/dist/platform/health/analyze.js +192 -0
  176. package/dist/platform/index.d.ts +27 -0
  177. package/dist/platform/index.js +89 -0
  178. package/dist/platform/knowledge/analyze.d.ts +20 -0
  179. package/dist/platform/knowledge/analyze.js +96 -0
  180. package/dist/platform/provenance/build.d.ts +44 -0
  181. package/dist/platform/provenance/build.js +65 -0
  182. package/dist/platform/readiness/scorecard.d.ts +5 -0
  183. package/dist/platform/readiness/scorecard.js +98 -0
  184. package/dist/platform/refactor/impact.d.ts +20 -0
  185. package/dist/platform/refactor/impact.js +60 -0
  186. package/dist/platform/reports/export.d.ts +12 -0
  187. package/dist/platform/reports/export.js +92 -0
  188. package/dist/platform/security/redact.d.ts +15 -0
  189. package/dist/platform/security/redact.js +87 -0
  190. package/dist/platform/sessions/store.d.ts +32 -0
  191. package/dist/platform/sessions/store.js +97 -0
  192. package/dist/platform/store.d.ts +6 -0
  193. package/dist/platform/store.js +57 -0
  194. package/dist/platform/test-impact/analyze.d.ts +50 -0
  195. package/dist/platform/test-impact/analyze.js +350 -0
  196. package/dist/platform/time-machine/compare.d.ts +17 -0
  197. package/dist/platform/time-machine/compare.js +62 -0
  198. package/dist/platform/tokens/plan.d.ts +28 -0
  199. package/dist/platform/tokens/plan.js +108 -0
  200. package/dist/platform/types.d.ts +120 -0
  201. package/dist/platform/types.js +5 -0
  202. package/dist/plugins/runtime.d.ts +36 -0
  203. package/dist/plugins/runtime.js +101 -0
  204. package/dist/plugins/sdk.d.ts +29 -0
  205. package/dist/plugins/sdk.js +114 -0
  206. package/dist/policy/compose.d.ts +34 -0
  207. package/dist/policy/compose.js +118 -0
  208. package/dist/policy/packs.d.ts +9 -0
  209. package/dist/policy/packs.js +91 -0
  210. package/dist/reporters/terminal/report.js +38 -5
  211. package/dist/security/paths.d.ts +21 -0
  212. package/dist/security/paths.js +105 -0
  213. package/dist/storage/postgres.d.ts +26 -0
  214. package/dist/storage/postgres.js +90 -0
  215. package/dist/storage/provider.d.ts +30 -0
  216. package/dist/storage/provider.js +109 -0
  217. package/dist/storage/sqlite.d.ts +33 -0
  218. package/dist/storage/sqlite.js +77 -0
  219. package/dist/team/auth.d.ts +35 -0
  220. package/dist/team/auth.js +70 -0
  221. package/dist/types/index.d.ts +5 -1
  222. package/dist/workspace/index.d.ts +69 -0
  223. package/dist/workspace/index.js +220 -0
  224. package/package.json +15 -5
@@ -0,0 +1,402 @@
1
+ import http from "node:http";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { getBrainStatus } from "../core/brain-cli/service.js";
5
+ import { listFixAudits } from "../core/fix/backup.js";
6
+ import { listNamedBaselines } from "../core/baseline/store.js";
7
+ import { detectMonorepo } from "../core/monorepo/detect.js";
8
+ import { scan } from "../core/scanner/scan.js";
9
+ import { resolveRepoRoot } from "../utils/path.js";
10
+ import { agentRegistry } from "../agents/registry.js";
11
+ import { listSessions } from "../platform/sessions/store.js";
12
+ import { platformDir, readJsonIfExists } from "../platform/store.js";
13
+ import { canAccess, loadLocalAuthConfig, resolveRole } from "../platform/auth/local.js";
14
+ import { EVALUATE_ONLY } from "../platform/firewall/evaluate.js";
15
+ import { redactSecrets, sanitizeFindingsForExport } from "../platform/security/redact.js";
16
+ import { buildIntelligenceGraph } from "../intelligence/graph/build.js";
17
+ import { analyzeGitIntelligence } from "../intelligence/git/analyze.js";
18
+ import { buildC4Views } from "../architecture/c4.js";
19
+ import { listKnowledge } from "../knowledge/store.js";
20
+ import { CONTRACTS_VERSION } from "../contracts/index.js";
21
+ import { collectOpsHealth } from "../ops/health.js";
22
+ import { listPolicyPacks } from "../policy/packs.js";
23
+ import { sanitizeForOutput } from "../utils/path.js";
24
+ function safeJsonError(error) {
25
+ const raw = error instanceof Error ? error.message : String(error);
26
+ const { text } = redactSecrets(sanitizeForOutput(raw));
27
+ return text.slice(0, 240);
28
+ }
29
+ function pathnameLooksHostile(pathname) {
30
+ const lower = pathname.toLowerCase();
31
+ const decoded = (() => {
32
+ try {
33
+ return decodeURIComponent(pathname);
34
+ }
35
+ catch {
36
+ return pathname;
37
+ }
38
+ })();
39
+ return (pathname.includes("\0") ||
40
+ decoded.includes("\0") ||
41
+ pathname.includes("..") ||
42
+ decoded.includes("..") ||
43
+ lower.includes("%2e%2e") ||
44
+ lower.includes("%2e.") ||
45
+ lower.includes(".%2e") ||
46
+ pathname.includes("\\") ||
47
+ decoded.includes("\\"));
48
+ }
49
+ function redactGraphSamples(nodes) {
50
+ return nodes.map((n) => {
51
+ const next = { ...n };
52
+ if (typeof next.label === "string") {
53
+ next.label = redactSecrets(next.label).text;
54
+ }
55
+ if (typeof next.path === "string") {
56
+ next.path = redactSecrets(next.path).text;
57
+ }
58
+ return next;
59
+ });
60
+ }
61
+ export function isLoopbackHost(host) {
62
+ const normalized = host.trim().toLowerCase();
63
+ return (normalized === "127.0.0.1" ||
64
+ normalized === "::1" ||
65
+ normalized === "localhost" ||
66
+ normalized === "0:0:0:0:0:0:0:1");
67
+ }
68
+ function htmlPage() {
69
+ return `<!doctype html>
70
+ <html lang="en">
71
+ <head>
72
+ <meta charset="utf-8" />
73
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
74
+ <title>AgentDoctor Dashboard</title>
75
+ <style>
76
+ :root { --bg:#0f1419; --fg:#e7ecf1; --muted:#9aa7b5; --accent:#3d9cfd; --card:#1a222c; }
77
+ * { box-sizing: border-box; }
78
+ body { margin:0; font-family: ui-sans-serif, system-ui, sans-serif; background:var(--bg); color:var(--fg); }
79
+ header { padding:1.25rem 1.5rem; border-bottom:1px solid #243040; }
80
+ h1 { margin:0; font-size:1.25rem; letter-spacing:0.02em; }
81
+ p { color:var(--muted); margin:0.35rem 0 0; }
82
+ main { padding:1.5rem; display:grid; gap:1rem; max-width:1100px; margin:0 auto; }
83
+ section { background:var(--card); border-radius:10px; padding:1rem 1.1rem; }
84
+ h2 { margin:0 0 0.75rem; font-size:1rem; }
85
+ pre { white-space:pre-wrap; word-break:break-word; font-size:0.85rem; color:#d5dde6; }
86
+ .muted { color:var(--muted); }
87
+ a { color:var(--accent); }
88
+ .notice { border-left:3px solid var(--accent); padding-left:0.75rem; margin-top:0.75rem; }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <header>
93
+ <h1>AgentDoctor 2.0</h1>
94
+ <p>Local read-only dashboard — Safety + Brain + intelligence. No cloud writes.</p>
95
+ <p class="notice muted">Action Policy Evaluator is evaluate-only. Query ?user= is a localDevIdentityHint only (not authentication). Team local-dev auth is separate and not SSO. OIDC JWT validation is a library path — full browser OAuth redirect is experimental.</p>
96
+ </header>
97
+ <main>
98
+ <nav aria-label="Views" style="display:flex;flex-wrap:wrap;gap:0.75rem;font-size:0.9rem">
99
+ <a href="#overview">Overview</a>
100
+ <a href="#scan">Safety scan</a>
101
+ <a href="#platform">Platform</a>
102
+ <a href="#brain">Brain</a>
103
+ <a href="#graph">Graph</a>
104
+ <a href="#knowledge">Knowledge</a>
105
+ <a href="#meta">Audit / baselines</a>
106
+ </nav>
107
+ <section id="overview">
108
+ <h2>Overview</h2>
109
+ <pre id="status" class="muted">Loading…</pre>
110
+ </section>
111
+ <section id="scan">
112
+ <h2>Safety scan</h2>
113
+ <pre id="scanBody" class="muted">Loading…</pre>
114
+ </section>
115
+ <section id="platform">
116
+ <h2>Platform snapshot (incl. test-impact)</h2>
117
+ <pre id="platformBody" class="muted">Loading…</pre>
118
+ </section>
119
+ <section id="brain">
120
+ <h2>Project Brain</h2>
121
+ <pre id="brainBody" class="muted">Loading…</pre>
122
+ </section>
123
+ <section id="graph">
124
+ <h2>Intelligence graph / git / C4</h2>
125
+ <pre id="graphBody" class="muted">Loading…</pre>
126
+ </section>
127
+ <section id="knowledge">
128
+ <h2>Governed knowledge</h2>
129
+ <pre id="knowledgeBody" class="muted">Loading…</pre>
130
+ </section>
131
+ <section id="meta">
132
+ <h2>Safe Fix audit / baselines / sessions</h2>
133
+ <pre id="metaBody" class="muted">Loading…</pre>
134
+ </section>
135
+ </main>
136
+ <script>
137
+ async function load() {
138
+ const [status, scan, brain, meta, platform, graph, knowledge] = await Promise.all([
139
+ fetch('/api/status').then(r => r.json()),
140
+ fetch('/api/scan').then(r => r.json()),
141
+ fetch('/api/brain').then(r => r.json()),
142
+ fetch('/api/meta').then(r => r.json()),
143
+ fetch('/api/platform').then(r => r.json()),
144
+ fetch('/api/v2/graph').then(r => r.json()),
145
+ fetch('/api/v2/knowledge').then(r => r.json()),
146
+ ]);
147
+ document.getElementById('status').textContent = JSON.stringify(status, null, 2);
148
+ document.getElementById('scanBody').textContent = JSON.stringify(scan, null, 2);
149
+ document.getElementById('brainBody').textContent = JSON.stringify(brain, null, 2);
150
+ document.getElementById('metaBody').textContent = JSON.stringify(meta, null, 2);
151
+ document.getElementById('platformBody').textContent = JSON.stringify(platform, null, 2);
152
+ document.getElementById('graphBody').textContent = JSON.stringify(graph, null, 2);
153
+ document.getElementById('knowledgeBody').textContent = JSON.stringify(knowledge, null, 2);
154
+ }
155
+ load().catch(err => {
156
+ document.getElementById('status').textContent = String(err);
157
+ });
158
+ </script>
159
+ </body>
160
+ </html>`;
161
+ }
162
+ function sendJson(res, status, body) {
163
+ const payload = JSON.stringify(body, null, 2);
164
+ res.writeHead(status, {
165
+ "Content-Type": "application/json; charset=utf-8",
166
+ "Cache-Control": "no-store",
167
+ });
168
+ res.end(payload);
169
+ }
170
+ /**
171
+ * Local read-only HTTP dashboard. Never applies Safe Fix or mutates the repo.
172
+ * Defaults to loopback binding; non-loopback requires explicit allowNonLoopback.
173
+ */
174
+ export async function startDashboardServer(options) {
175
+ const root = resolveRepoRoot(options.root);
176
+ const host = options.host ?? "127.0.0.1";
177
+ const port = options.port ?? 8787;
178
+ if (!isLoopbackHost(host) && options.allowNonLoopback !== true) {
179
+ throw new Error(`Refusing to bind dashboard to non-loopback host "${host}". Pass allowNonLoopback / --allow-non-loopback to override (unsafe; not an enterprise security boundary).`);
180
+ }
181
+ const server = http.createServer(async (req, res) => {
182
+ try {
183
+ const rawUrl = req.url ?? "/";
184
+ if (pathnameLooksHostile(rawUrl.split("?")[0] ?? rawUrl)) {
185
+ sendJson(res, 400, { error: "invalid path" });
186
+ return;
187
+ }
188
+ const url = new URL(rawUrl, `http://${host}:${port}`);
189
+ if (req.method !== "GET") {
190
+ sendJson(res, 405, { error: "read-only dashboard; GET only" });
191
+ return;
192
+ }
193
+ if (pathnameLooksHostile(url.pathname)) {
194
+ sendJson(res, 400, { error: "invalid path" });
195
+ return;
196
+ }
197
+ if (url.pathname === "/" || url.pathname === "/index.html") {
198
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
199
+ res.end(htmlPage());
200
+ return;
201
+ }
202
+ if (url.pathname === "/api/status") {
203
+ const mono = await detectMonorepo(root);
204
+ const health = await collectOpsHealth(root);
205
+ sendJson(res, 200, {
206
+ root,
207
+ readOnly: true,
208
+ host,
209
+ loopbackOnly: isLoopbackHost(host),
210
+ contractsVersion: CONTRACTS_VERSION,
211
+ roleNote: "Query ?user= is a localDevIdentityHint only — not authentication. Privileged role elevation requires AGENTDOCTOR_ALLOW_LOCAL_IDENTITY_HINT=1",
212
+ actionPolicyNote: EVALUATE_ONLY,
213
+ adapters: agentRegistry.map((a) => a.id),
214
+ policyPacks: listPolicyPacks(),
215
+ ops: health,
216
+ monorepo: {
217
+ isMonorepo: mono.isMonorepo,
218
+ tool: mono.tool,
219
+ packages: mono.packages.length,
220
+ },
221
+ });
222
+ return;
223
+ }
224
+ if (url.pathname === "/api/scan") {
225
+ const result = await scan({ cwd: root });
226
+ sendJson(res, 200, {
227
+ summary: result.summary,
228
+ scores: result.scores,
229
+ findingCount: result.findings.length,
230
+ findings: result.findings.slice(0, 50).map((f) => ({
231
+ id: f.id,
232
+ ruleId: f.ruleId,
233
+ severity: f.severity,
234
+ title: f.title,
235
+ path: f.evidence?.path ?? null,
236
+ })),
237
+ });
238
+ return;
239
+ }
240
+ if (url.pathname === "/api/brain") {
241
+ sendJson(res, 200, await getBrainStatus(root));
242
+ return;
243
+ }
244
+ if (url.pathname === "/api/meta") {
245
+ const audits = await listFixAudits(root);
246
+ const baselines = await listNamedBaselines(root);
247
+ const sessions = await listSessions(root);
248
+ sendJson(res, 200, {
249
+ fixAudits: audits.slice(0, 20).map((a) => ({
250
+ id: a.id,
251
+ mode: a.mode,
252
+ createdAt: a.createdAt,
253
+ entryCount: a.entries.length,
254
+ })),
255
+ baselines,
256
+ sessions: sessions.slice(0, 50),
257
+ });
258
+ return;
259
+ }
260
+ if (url.pathname === "/api/platform") {
261
+ const auth = await loadLocalAuthConfig(root);
262
+ const identityHint = url.searchParams.get("user");
263
+ const allowLocalHint = process.env.AGENTDOCTOR_ALLOW_LOCAL_IDENTITY_HINT === "1";
264
+ // Default: deny privileged elevation. ?user= is a label only unless opt-in env is set.
265
+ let role = auth.defaultRole;
266
+ const localDevIdentityHint = identityHint && identityHint.trim() ? identityHint.trim() : null;
267
+ if (localDevIdentityHint && allowLocalHint) {
268
+ role = resolveRole(auth, localDevIdentityHint);
269
+ }
270
+ if (!canAccess(role, "read-findings")) {
271
+ sendJson(res, 403, {
272
+ error: "forbidden",
273
+ localDevIdentityHint,
274
+ notice: "?user= is not authentication; privileged ops denied without AGENTDOCTOR_ALLOW_LOCAL_IDENTITY_HINT=1",
275
+ });
276
+ return;
277
+ }
278
+ const snapshot = await readJsonIfExists(path.join(platformDir(root), "snapshots", "latest.json"));
279
+ const canExport = canAccess(role, "export-reports");
280
+ const safeFindings = snapshot
281
+ ? sanitizeFindingsForExport(snapshot.findings).slice(0, 30)
282
+ : [];
283
+ sendJson(res, 200, {
284
+ role,
285
+ localDevIdentityHint,
286
+ localIdentityHintElevated: Boolean(localDevIdentityHint && allowLocalHint),
287
+ hasSnapshot: snapshot !== null,
288
+ findingCount: snapshot?.findings.length ?? 0,
289
+ graphNodes: snapshot?.graph.nodes.length ?? 0,
290
+ readiness: snapshot?.readiness.categories ?? [],
291
+ testImpact: snapshot?.testImpact
292
+ ? {
293
+ gitAvailable: snapshot.testImpact.gitAvailable,
294
+ changedFiles: snapshot.testImpact.changedFiles.length,
295
+ recommendedTests: snapshot.testImpact.recommendedTests.length,
296
+ missingTestWarnings: snapshot.testImpact.missingTestWarnings.length,
297
+ relatedModules: snapshot.testImpact.relatedModules,
298
+ skipRisk: snapshot.testImpact.skipRisk,
299
+ sampleRecommended: canExport
300
+ ? snapshot.testImpact.recommendedTests.slice(0, 20)
301
+ : [],
302
+ sampleMissing: canExport
303
+ ? snapshot.testImpact.missingTestWarnings.slice(0, 10)
304
+ : [],
305
+ }
306
+ : null,
307
+ limitations: [
308
+ ...(snapshot?.limitations ?? [
309
+ "No platform snapshot yet — run: agentdoctor platform scan",
310
+ ]),
311
+ "?user= is localDevIdentityHint only — not authentication",
312
+ allowLocalHint
313
+ ? "AGENTDOCTOR_ALLOW_LOCAL_IDENTITY_HINT=1 enabled — hint may elevate local roles (still not SSO)"
314
+ : "Privileged role elevation from ?user= denied unless AGENTDOCTOR_ALLOW_LOCAL_IDENTITY_HINT=1",
315
+ EVALUATE_ONLY,
316
+ ],
317
+ sampleFindings: canExport
318
+ ? safeFindings.map((f) => ({
319
+ id: f.id,
320
+ module: f.module,
321
+ severity: f.severity,
322
+ title: f.title,
323
+ }))
324
+ : [],
325
+ });
326
+ return;
327
+ }
328
+ if (url.pathname === "/api/v2/graph") {
329
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
330
+ sendJson(res, 200, {
331
+ contractsVersion: CONTRACTS_VERSION,
332
+ builder: graph.builder,
333
+ nodeCount: graph.nodes.length,
334
+ edgeCount: graph.edges.length,
335
+ sampleNodes: redactGraphSamples(graph.nodes.slice(0, 40)),
336
+ sampleEdges: graph.edges.slice(0, 40),
337
+ limitations: graph.limitations,
338
+ });
339
+ return;
340
+ }
341
+ if (url.pathname === "/api/v2/health") {
342
+ sendJson(res, 200, await analyzeGitIntelligence(root));
343
+ return;
344
+ }
345
+ if (url.pathname === "/api/v2/c4") {
346
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
347
+ sendJson(res, 200, {
348
+ views: buildC4Views(graph),
349
+ label: "Inferred/proposed — not approved architecture facts",
350
+ });
351
+ return;
352
+ }
353
+ if (url.pathname === "/api/v2/knowledge") {
354
+ const records = await listKnowledge(root);
355
+ sendJson(res, 200, {
356
+ count: records.length,
357
+ records: records.slice(0, 100).map((r) => ({
358
+ id: r.id,
359
+ title: r.title,
360
+ status: r.status,
361
+ version: r.version,
362
+ })),
363
+ });
364
+ return;
365
+ }
366
+ if (url.pathname === "/api/v2/projects" || url.pathname === "/api/v2/workspaces") {
367
+ sendJson(res, 200, {
368
+ mode: "local-single-repo",
369
+ root,
370
+ notice: "Multi-repo workspaces are partially implemented (storage abstraction). Cloud org workspaces unsupported.",
371
+ });
372
+ return;
373
+ }
374
+ sendJson(res, 404, { error: "not found" });
375
+ }
376
+ catch (error) {
377
+ sendJson(res, 500, { error: safeJsonError(error) });
378
+ }
379
+ });
380
+ await new Promise((resolve, reject) => {
381
+ server.listen(port, host, () => resolve());
382
+ server.on("error", reject);
383
+ });
384
+ const address = server.address();
385
+ const boundPort = typeof address === "object" && address && typeof address.port === "number"
386
+ ? address.port
387
+ : port;
388
+ return {
389
+ host,
390
+ port: boundPort,
391
+ close: async () => new Promise((resolve, reject) => {
392
+ server.close((err) => (err ? reject(err) : resolve()));
393
+ }),
394
+ };
395
+ }
396
+ /** Write a static snapshot HTML for offline viewing (optional helper). */
397
+ export async function writeDashboardSnapshot(rootInput, outputPath) {
398
+ const absolute = path.resolve(outputPath);
399
+ await fs.mkdir(path.dirname(absolute), { recursive: true });
400
+ await fs.writeFile(absolute, htmlPage(), "utf8");
401
+ return absolute;
402
+ }
@@ -0,0 +1,67 @@
1
+ import type { PolicyDecisionContract } from "../contracts/index.js";
2
+ export type ExecutionStatus = "executed" | "blocked-by-enforcement" | "not-executed" | "execution-failed";
3
+ export interface EnforcementResult {
4
+ decision: PolicyDecisionContract;
5
+ enforced: boolean;
6
+ notice: string;
7
+ stdout?: string;
8
+ stderr?: string;
9
+ exitCode?: number | null;
10
+ durationMs?: number;
11
+ }
12
+ /**
13
+ * Parse a command string into argv without invoking a shell.
14
+ * Supports simple double/single quotes; rejects unclosed quotes.
15
+ */
16
+ export declare function parseArgv(command: string): string[];
17
+ export declare function buildAllowlistedEnv(source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
18
+ /**
19
+ * Reject path-like args that resolve outside the repository root.
20
+ */
21
+ export declare function assertArgsInsideRoot(args: string[], root: string): void;
22
+ /**
23
+ * AgentDoctor-controlled command runner.
24
+ * Executes only when executeIfAllowed===true AND policy decision===allow.
25
+ * Default path uses shell:false with argv parsing (no shell metacharacters).
26
+ */
27
+ export declare function runControlledCommand(options: {
28
+ root: string;
29
+ command?: string;
30
+ argv?: string[];
31
+ failClosed?: boolean;
32
+ /** When true, execute only if policy allows. */
33
+ executeIfAllowed?: boolean;
34
+ /** Explicit high-risk shell mode — requires policy allow as well. */
35
+ useShell?: boolean;
36
+ timeoutMs?: number;
37
+ maxBuffer?: number;
38
+ }): Promise<EnforcementResult>;
39
+ /** Append-only tamper-evident audit chain (HMAC over previous hash). */
40
+ export declare function appendAuditChain(previousHash: string | null, record: unknown, secret?: string): {
41
+ hash: string;
42
+ payload: string;
43
+ };
44
+ export declare function auditRecordId(parts: string): string;
45
+ /**
46
+ * Explain what the controlled runner would do for a command (evaluate-only).
47
+ * Does not execute.
48
+ */
49
+ export declare function explainControlledRun(options: {
50
+ root: string;
51
+ command?: string;
52
+ argv?: string[];
53
+ failClosed?: boolean;
54
+ useShell?: boolean;
55
+ }): Promise<{
56
+ command: string;
57
+ argv: string[];
58
+ useShell: boolean;
59
+ decision: string;
60
+ reason: string;
61
+ policyId: string | null;
62
+ riskLevel: string;
63
+ wouldExecute: boolean;
64
+ executionResult: "not-executed";
65
+ notice: string;
66
+ meaning: string;
67
+ }>;