@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,192 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { resolveRepoRoot } from "../../utils/path.js";
5
+ function fid(module, key) {
6
+ return `health_${createHash("sha1").update(`${module}:${key}`).digest("hex").slice(0, 12)}`;
7
+ }
8
+ /**
9
+ * Module B — Code health & risk (deterministic heuristics with evidence).
10
+ */
11
+ export async function analyzeCodeHealth(rootInput, graph) {
12
+ const root = resolveRepoRoot(rootInput);
13
+ const findings = [];
14
+ const fileNodes = graph.nodes.filter((n) => n.kind === "file" || n.kind === "test");
15
+ for (const node of fileNodes.slice(0, 800)) {
16
+ if (!node.path)
17
+ continue;
18
+ const absolute = path.join(root, node.path);
19
+ let content;
20
+ let size;
21
+ try {
22
+ const handle = await fs.open(absolute, "r");
23
+ try {
24
+ const st = await handle.stat();
25
+ size = st.size;
26
+ if (size > 512 * 1024) {
27
+ findings.push({
28
+ id: fid("filesize", node.path),
29
+ module: "code-health",
30
+ severity: "medium",
31
+ title: "Large source file",
32
+ message: `${node.path} is ${size} bytes`,
33
+ recommendation: "Split into smaller modules to reduce change risk",
34
+ confidence: 0.9,
35
+ evidence: [{ kind: "verified", path: node.path, detail: `sizeBytes=${size}` }],
36
+ });
37
+ continue;
38
+ }
39
+ if (!/\.(ts|tsx|js|jsx|py|go)$/i.test(node.path))
40
+ continue;
41
+ content = await handle.readFile("utf8");
42
+ }
43
+ finally {
44
+ await handle.close();
45
+ }
46
+ }
47
+ catch {
48
+ continue;
49
+ }
50
+ const lines = content.split(/\r?\n/);
51
+ if (lines.length > 500) {
52
+ findings.push({
53
+ id: fid("loc", node.path),
54
+ module: "code-health",
55
+ severity: "medium",
56
+ title: "High line count",
57
+ message: `${node.path} has ${lines.length} lines`,
58
+ recommendation: "Consider modularization",
59
+ confidence: 0.85,
60
+ evidence: [{ kind: "verified", path: node.path, detail: `lines=${lines.length}` }],
61
+ });
62
+ }
63
+ let depth = 0;
64
+ let maxDepth = 0;
65
+ for (const line of lines) {
66
+ for (const ch of line) {
67
+ if (ch === "{" || ch === "(")
68
+ depth += 1;
69
+ if (ch === "}" || ch === ")")
70
+ depth = Math.max(0, depth - 1);
71
+ maxDepth = Math.max(maxDepth, depth);
72
+ }
73
+ }
74
+ if (maxDepth >= 8) {
75
+ findings.push({
76
+ id: fid("complexity", node.path),
77
+ module: "code-health",
78
+ severity: "high",
79
+ title: "High nesting / complexity heuristic",
80
+ message: `${node.path} nesting depth heuristic=${maxDepth}`,
81
+ recommendation: "Reduce nesting; extract helpers",
82
+ confidence: 0.55,
83
+ evidence: [
84
+ {
85
+ kind: "inferred",
86
+ path: node.path,
87
+ detail: `maxNesting=${maxDepth} (brace/paren heuristic, not full cyclomatic)`,
88
+ },
89
+ ],
90
+ falsePositiveWarning: "Nesting depth is a proxy; verify with a language-aware analyzer",
91
+ });
92
+ }
93
+ if (/catch\s*\([^)]*\)\s*\{\s*\}/m.test(content) || /catch\s*\{\s*\}/m.test(content)) {
94
+ findings.push({
95
+ id: fid("empty-catch", node.path),
96
+ module: "code-health",
97
+ severity: "medium",
98
+ title: "Empty catch block",
99
+ message: `${node.path} contains an empty catch`,
100
+ recommendation: "Log or handle errors explicitly",
101
+ confidence: 0.7,
102
+ evidence: [{ kind: "inferred", path: node.path, detail: "empty catch pattern" }],
103
+ });
104
+ }
105
+ }
106
+ // Circular import heuristic among relative edges (file→file via resolved relative specs)
107
+ const fileByPath = new Map(graph.nodes
108
+ .filter((n) => n.path && (n.kind === "file" || n.kind === "test"))
109
+ .map((n) => [n.path, n.id]));
110
+ const adj = new Map();
111
+ for (const edge of graph.edges.filter((e) => e.kind === "imports" && e.evidence === "verified")) {
112
+ const from = edge.from;
113
+ const toNode = graph.nodes.find((n) => n.id === edge.to);
114
+ const spec = typeof toNode?.meta?.specifier === "string" ? toNode.meta.specifier : toNode?.label;
115
+ if (!spec || typeof spec !== "string" || !spec.startsWith("."))
116
+ continue;
117
+ const fromPath = graph.nodes.find((n) => n.id === from)?.path;
118
+ if (!fromPath)
119
+ continue;
120
+ const resolvedRel = path.posix.normalize(path.posix.join(path.posix.dirname(fromPath), spec));
121
+ const candidates = [
122
+ resolvedRel,
123
+ `${resolvedRel}.ts`,
124
+ `${resolvedRel}.js`,
125
+ `${resolvedRel}/index.ts`,
126
+ ];
127
+ for (const cand of candidates) {
128
+ const toId = fileByPath.get(cand);
129
+ if (toId) {
130
+ if (!adj.has(from))
131
+ adj.set(from, new Set());
132
+ adj.get(from).add(toId);
133
+ break;
134
+ }
135
+ }
136
+ }
137
+ const visiting = new Set();
138
+ const visited = new Set();
139
+ const reportCycle = (stack) => {
140
+ const paths = stack
141
+ .map((id) => graph.nodes.find((n) => n.id === id)?.path)
142
+ .filter(Boolean)
143
+ .join(" -> ");
144
+ findings.push({
145
+ id: fid("cycle", stack.join("|")),
146
+ module: "code-health",
147
+ severity: "high",
148
+ title: "Possible circular import",
149
+ message: paths || stack.join(" -> "),
150
+ recommendation: "Break the cycle with inversion of control or a shared module",
151
+ confidence: 0.5,
152
+ evidence: [{ kind: "inferred", detail: `cycleLen=${stack.length}` }],
153
+ falsePositiveWarning: "Relative import resolution is heuristic; confirm with a bundler/graph tool",
154
+ });
155
+ };
156
+ const dfs = (node, stack) => {
157
+ if (visiting.has(node)) {
158
+ const idx = stack.indexOf(node);
159
+ if (idx >= 0)
160
+ reportCycle(stack.slice(idx));
161
+ return;
162
+ }
163
+ if (visited.has(node))
164
+ return;
165
+ visiting.add(node);
166
+ stack.push(node);
167
+ for (const next of adj.get(node) ?? [])
168
+ dfs(next, stack);
169
+ stack.pop();
170
+ visiting.delete(node);
171
+ visited.add(node);
172
+ };
173
+ for (const node of adj.keys())
174
+ dfs(node, []);
175
+ findings.sort((a, b) => a.id.localeCompare(b.id));
176
+ return findings;
177
+ }
178
+ export function scoreFileRisk(findings, filePath) {
179
+ const related = findings.filter((f) => f.evidence.some((e) => e.path === filePath));
180
+ let score = 0;
181
+ for (const f of related) {
182
+ if (f.severity === "critical")
183
+ score += 40;
184
+ else if (f.severity === "high")
185
+ score += 25;
186
+ else if (f.severity === "medium")
187
+ score += 12;
188
+ else
189
+ score += 4;
190
+ }
191
+ return Math.min(100, score);
192
+ }
@@ -0,0 +1,27 @@
1
+ import { analyzeKnowledgeGovernance } from "./knowledge/analyze.js";
2
+ import { type TestImpactReport } from "./test-impact/analyze.js";
3
+ import type { PlatformSnapshot } from "./types.js";
4
+ export interface PlatformScanResult {
5
+ snapshot: PlatformSnapshot;
6
+ reportPaths: Record<string, string>;
7
+ testImpact: TestImpactReport;
8
+ knowledge: Awaited<ReturnType<typeof analyzeKnowledgeGovernance>>;
9
+ }
10
+ /**
11
+ * Orchestrate AgentDoctor 2.0 local platform scan (Modules A–O subset).
12
+ */
13
+ export declare function runPlatformScan(rootInput: string): Promise<PlatformScanResult>;
14
+ export * from "./types.js";
15
+ export { buildRepositoryGraph } from "./graph/build.js";
16
+ export { evaluateAgentAction, loadFirewallPolicy, validateFirewallPolicy, EVALUATE_ONLY, } from "./firewall/evaluate.js";
17
+ export { createSession, appendSessionEvent, endSession, loadSession, listSessions, exportSessionMarkdown, } from "./sessions/store.js";
18
+ export { buildProvenance, saveProvenance } from "./provenance/build.js";
19
+ export { planContext } from "./tokens/plan.js";
20
+ export { analyzeRenameImpact } from "./refactor/impact.js";
21
+ export { compareCommits, listRecentCommits } from "./time-machine/compare.js";
22
+ export { canAccess, resolveRole, loadLocalAuthConfig } from "./auth/local.js";
23
+ export { analyzeTestImpact, persistTestImpactReport, formatTestImpactHuman, } from "./test-impact/analyze.js";
24
+ export { redactSecrets, redactEvidenceDetail, sanitizeFindingsForExport, SECRET_REDACTION_MARKER, } from "./security/redact.js";
25
+ export { analyzeKnowledgeGovernance } from "./knowledge/analyze.js";
26
+ export { analyzeContextSecurity } from "./context-security/analyze.js";
27
+ export { exportReports } from "./reports/export.js";
@@ -0,0 +1,89 @@
1
+ import { buildRepositoryGraph } from "./graph/build.js";
2
+ import { analyzeCodeHealth } from "./health/analyze.js";
3
+ import { analyzeContextSecurity } from "./context-security/analyze.js";
4
+ import { analyzeKnowledgeGovernance } from "./knowledge/analyze.js";
5
+ import { analyzeArchitectureDrift } from "./architecture/drift.js";
6
+ import { analyzeAiChangeQuality } from "./ai-quality/analyze.js";
7
+ import { analyzeTestImpact, persistTestImpactReport, } from "./test-impact/analyze.js";
8
+ import { buildReadinessScorecard } from "./readiness/scorecard.js";
9
+ import { exportReports } from "./reports/export.js";
10
+ import { ensurePlatformDir, writeJsonArtifact } from "./store.js";
11
+ import { ensureAuthConfig } from "./auth/local.js";
12
+ import { loadFirewallPolicy } from "./firewall/evaluate.js";
13
+ import { sanitizeFindingsForExport } from "./security/redact.js";
14
+ import { resolveRepoRoot } from "../utils/path.js";
15
+ function toTestImpactSnapshot(report) {
16
+ return {
17
+ gitAvailable: report.gitAvailable,
18
+ mode: report.mode,
19
+ changedFiles: report.changedFiles,
20
+ recommendedTests: report.recommendedTests,
21
+ relatedModules: report.relatedModules,
22
+ missingTestWarnings: report.missingTestWarnings,
23
+ uncoveredAreas: report.uncoveredAreas,
24
+ skipRisk: report.skipRisk,
25
+ coverage: report.coverage,
26
+ limitations: report.limitations,
27
+ };
28
+ }
29
+ /**
30
+ * Orchestrate AgentDoctor 2.0 local platform scan (Modules A–O subset).
31
+ */
32
+ export async function runPlatformScan(rootInput) {
33
+ const root = resolveRepoRoot(rootInput);
34
+ await ensurePlatformDir(root);
35
+ await ensureAuthConfig(root);
36
+ await loadFirewallPolicy(root);
37
+ const graph = await buildRepositoryGraph(root);
38
+ const health = await analyzeCodeHealth(root, graph);
39
+ const ctxsec = await analyzeContextSecurity(root);
40
+ const knowledge = await analyzeKnowledgeGovernance(root);
41
+ const drift = await analyzeArchitectureDrift(root, graph);
42
+ const aiq = await analyzeAiChangeQuality({ root, aiAssisted: "unknown" });
43
+ const testImpact = await analyzeTestImpact(root);
44
+ const findings = sanitizeFindingsForExport([
45
+ ...health,
46
+ ...ctxsec,
47
+ ...knowledge.findings,
48
+ ...drift,
49
+ ...aiq,
50
+ ]).sort((a, b) => a.id.localeCompare(b.id));
51
+ const readiness = await buildReadinessScorecard(root, findings);
52
+ const testImpactSnapshot = toTestImpactSnapshot(testImpact);
53
+ const snapshot = {
54
+ version: "2.0",
55
+ root,
56
+ generatedAt: new Date().toISOString(),
57
+ graph,
58
+ findings,
59
+ readiness,
60
+ testImpact: testImpactSnapshot,
61
+ limitations: [
62
+ ...graph.limitations,
63
+ ...knowledge.limitations,
64
+ ...readiness.limitations,
65
+ ...testImpact.limitations,
66
+ "Action Policy Evaluator is evaluate-only — no commands executed and no agents intercepted",
67
+ "Local ?user= roles are not authentication; cloud SSO/RBAC are out of scope",
68
+ ],
69
+ };
70
+ await writeJsonArtifact(root, `snapshots/latest.json`, snapshot);
71
+ const testImpactPath = await persistTestImpactReport(root, testImpact);
72
+ const reportPaths = await exportReports({ root, findings, readiness, snapshot });
73
+ reportPaths.testImpact = testImpactPath;
74
+ return { snapshot, reportPaths, testImpact, knowledge };
75
+ }
76
+ export * from "./types.js";
77
+ export { buildRepositoryGraph } from "./graph/build.js";
78
+ export { evaluateAgentAction, loadFirewallPolicy, validateFirewallPolicy, EVALUATE_ONLY, } from "./firewall/evaluate.js";
79
+ export { createSession, appendSessionEvent, endSession, loadSession, listSessions, exportSessionMarkdown, } from "./sessions/store.js";
80
+ export { buildProvenance, saveProvenance } from "./provenance/build.js";
81
+ export { planContext } from "./tokens/plan.js";
82
+ export { analyzeRenameImpact } from "./refactor/impact.js";
83
+ export { compareCommits, listRecentCommits } from "./time-machine/compare.js";
84
+ export { canAccess, resolveRole, loadLocalAuthConfig } from "./auth/local.js";
85
+ export { analyzeTestImpact, persistTestImpactReport, formatTestImpactHuman, } from "./test-impact/analyze.js";
86
+ export { redactSecrets, redactEvidenceDetail, sanitizeFindingsForExport, SECRET_REDACTION_MARKER, } from "./security/redact.js";
87
+ export { analyzeKnowledgeGovernance } from "./knowledge/analyze.js";
88
+ export { analyzeContextSecurity } from "./context-security/analyze.js";
89
+ export { exportReports } from "./reports/export.js";
@@ -0,0 +1,20 @@
1
+ import type { PlatformFinding } from "../types.js";
2
+ export interface KnowledgeSource {
3
+ id: string;
4
+ path: string;
5
+ sourceType: "readme" | "docs" | "agent-instructions" | "other";
6
+ mtime: string;
7
+ sizeBytes: number;
8
+ trustLevel: "high" | "medium" | "low" | "unverified";
9
+ owner: string | null;
10
+ }
11
+ export interface KnowledgeReport {
12
+ root: string;
13
+ sources: KnowledgeSource[];
14
+ findings: PlatformFinding[];
15
+ limitations: string[];
16
+ }
17
+ /**
18
+ * Module G — Knowledge governance MVP.
19
+ */
20
+ export declare function analyzeKnowledgeGovernance(rootInput: string): Promise<KnowledgeReport>;
@@ -0,0 +1,96 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { resolveRepoRoot, toPosixRelative } from "../../utils/path.js";
5
+ /**
6
+ * Module G — Knowledge governance MVP.
7
+ */
8
+ export async function analyzeKnowledgeGovernance(rootInput) {
9
+ const root = resolveRepoRoot(rootInput);
10
+ const sources = [];
11
+ const findings = [];
12
+ const docs = [];
13
+ async function consider(rel, sourceType) {
14
+ const absolute = path.join(root, rel);
15
+ try {
16
+ const st = await fs.stat(absolute);
17
+ if (!st.isFile())
18
+ return;
19
+ const id = `know_${createHash("sha1").update(rel).digest("hex").slice(0, 10)}`;
20
+ const ageDays = (Date.now() - st.mtimeMs) / (86400 * 1000);
21
+ const trust = sourceType === "readme" ? "medium" : ageDays > 365 ? "low" : "unverified";
22
+ sources.push({
23
+ id,
24
+ path: rel,
25
+ sourceType,
26
+ mtime: st.mtime.toISOString(),
27
+ sizeBytes: st.size,
28
+ trustLevel: trust,
29
+ owner: null,
30
+ });
31
+ docs.push(rel);
32
+ if (ageDays > 365) {
33
+ findings.push({
34
+ id: `know_stale_${id}`,
35
+ module: "knowledge",
36
+ severity: "low",
37
+ title: "Stale documentation",
38
+ message: `${rel} last modified ${Math.floor(ageDays)} days ago`,
39
+ recommendation: "Review for accuracy against current code",
40
+ confidence: 0.8,
41
+ evidence: [{ kind: "verified", path: rel, detail: `mtime=${st.mtime.toISOString()}` }],
42
+ });
43
+ }
44
+ if (st.size === 0) {
45
+ findings.push({
46
+ id: `know_empty_${id}`,
47
+ module: "knowledge",
48
+ severity: "medium",
49
+ title: "Empty documentation file",
50
+ message: rel,
51
+ recommendation: "Fill or remove empty docs",
52
+ confidence: 1,
53
+ evidence: [{ kind: "verified", path: rel, detail: "size=0" }],
54
+ });
55
+ }
56
+ }
57
+ catch {
58
+ // missing
59
+ }
60
+ }
61
+ await consider("README.md", "readme");
62
+ await consider("AGENTS.md", "agent-instructions");
63
+ await consider("CLAUDE.md", "agent-instructions");
64
+ await consider("docs/README.md", "docs");
65
+ try {
66
+ const docsDir = path.join(root, "docs");
67
+ const entries = await fs.readdir(docsDir);
68
+ for (const e of entries.filter((x) => x.endsWith(".md")).slice(0, 40)) {
69
+ await consider(toPosixRelative(root, path.join(docsDir, e)), "docs");
70
+ }
71
+ }
72
+ catch {
73
+ // optional
74
+ }
75
+ if (sources.filter((s) => s.owner === null).length === sources.length && sources.length > 0) {
76
+ findings.push({
77
+ id: "know_no_owners",
78
+ module: "knowledge",
79
+ severity: "info",
80
+ title: "No documentation owners detected",
81
+ message: "CODEOWNERS/MAINTAINERS not linked to knowledge sources in this MVP",
82
+ recommendation: "Add CODEOWNERS entries for docs/",
83
+ confidence: 0.6,
84
+ evidence: [{ kind: "unknown", detail: "owner metadata unavailable" }],
85
+ });
86
+ }
87
+ return {
88
+ root,
89
+ sources: sources.sort((a, b) => a.path.localeCompare(b.path)),
90
+ findings: findings.sort((a, b) => a.id.localeCompare(b.id)),
91
+ limitations: [
92
+ "Contradiction detection between docs and code is limited in this MVP",
93
+ "Ownership requires CODEOWNERS integration (extension point)",
94
+ ],
95
+ };
96
+ }
@@ -0,0 +1,44 @@
1
+ import type { EvidenceKind } from "../types.js";
2
+ export interface ProvenanceRecord {
3
+ id: string;
4
+ repository: string;
5
+ branch: string | null;
6
+ commit: string | null;
7
+ pullRequest: string | null;
8
+ file: string;
9
+ lineRange?: {
10
+ start: number;
11
+ end: number;
12
+ };
13
+ agent: string | null;
14
+ model: string | null;
15
+ taskRef: string | null;
16
+ toolCalls: string[];
17
+ testsExecuted: string[];
18
+ reviewer: string | null;
19
+ approvalStatus: "unknown" | "approved" | "pending" | "rejected";
20
+ risk: string;
21
+ fields: Record<string, {
22
+ value: string | null;
23
+ kind: EvidenceKind;
24
+ }>;
25
+ chain: string[];
26
+ }
27
+ /**
28
+ * Module E — AI change provenance.
29
+ * Never fabricates missing metadata; marks unknown explicitly.
30
+ */
31
+ export declare function buildProvenance(options: {
32
+ root: string;
33
+ file: string;
34
+ agent?: string;
35
+ model?: string;
36
+ taskRef?: string;
37
+ toolCalls?: string[];
38
+ testsExecuted?: string[];
39
+ reviewer?: string;
40
+ approvalStatus?: ProvenanceRecord["approvalStatus"];
41
+ lineStart?: number;
42
+ lineEnd?: number;
43
+ }): ProvenanceRecord;
44
+ export declare function saveProvenance(root: string, record: ProvenanceRecord): Promise<string>;
@@ -0,0 +1,65 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import { writeJsonArtifact } from "../store.js";
4
+ import { resolveRepoRoot } from "../../utils/path.js";
5
+ function git(root, args) {
6
+ const r = spawnSync("git", args, { cwd: root, encoding: "utf8" });
7
+ return r.status === 0 ? r.stdout.trim() : null;
8
+ }
9
+ /**
10
+ * Module E — AI change provenance.
11
+ * Never fabricates missing metadata; marks unknown explicitly.
12
+ */
13
+ export function buildProvenance(options) {
14
+ const root = resolveRepoRoot(options.root);
15
+ const commit = git(root, ["rev-parse", "HEAD"]);
16
+ const branch = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
17
+ const field = (value, kind) => ({
18
+ value: value ?? null,
19
+ kind: value ? kind : "unknown",
20
+ });
21
+ const record = {
22
+ id: `prov_${createHash("sha1")
23
+ .update(`${options.file}:${commit ?? randomUUID()}`)
24
+ .digest("hex")
25
+ .slice(0, 12)}`,
26
+ repository: root,
27
+ branch,
28
+ commit,
29
+ pullRequest: null,
30
+ file: options.file,
31
+ agent: options.agent ?? null,
32
+ model: options.model ?? null,
33
+ taskRef: options.taskRef ?? null,
34
+ toolCalls: options.toolCalls ?? [],
35
+ testsExecuted: options.testsExecuted ?? [],
36
+ reviewer: options.reviewer ?? null,
37
+ approvalStatus: options.approvalStatus ?? "unknown",
38
+ risk: "unknown",
39
+ fields: {
40
+ repository: field(root, "verified"),
41
+ branch: field(branch, "verified"),
42
+ commit: field(commit, "verified"),
43
+ pullRequest: field(null, "unknown"),
44
+ agent: field(options.agent ?? null, options.agent ? "inferred" : "unknown"),
45
+ model: field(options.model ?? null, options.model ? "inferred" : "unknown"),
46
+ reviewer: field(options.reviewer ?? null, options.reviewer ? "verified" : "unknown"),
47
+ },
48
+ chain: [
49
+ "Request",
50
+ options.agent ? "Agent" : "Agent(unknown)",
51
+ "Tool calls",
52
+ "Files changed",
53
+ options.testsExecuted?.length ? "Tests" : "Tests(unknown)",
54
+ options.reviewer ? "Review" : "Review(unknown)",
55
+ commit ? "Commit" : "Commit(unknown)",
56
+ ],
57
+ };
58
+ if (options.lineStart !== undefined && options.lineEnd !== undefined) {
59
+ record.lineRange = { start: options.lineStart, end: options.lineEnd };
60
+ }
61
+ return record;
62
+ }
63
+ export async function saveProvenance(root, record) {
64
+ return writeJsonArtifact(root, `provenance/${record.id}.json`, record);
65
+ }
@@ -0,0 +1,5 @@
1
+ import type { PlatformFinding, ReadinessReport } from "../types.js";
2
+ /**
3
+ * Module N — Agent readiness scorecard (evidence-backed categories).
4
+ */
5
+ export declare function buildReadinessScorecard(rootInput: string, extraFindings?: PlatformFinding[]): Promise<ReadinessReport>;
@@ -0,0 +1,98 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { scan } from "../../core/scanner/scan.js";
4
+ import { resolveRepoRoot } from "../../utils/path.js";
5
+ /**
6
+ * Module N — Agent readiness scorecard (evidence-backed categories).
7
+ */
8
+ export async function buildReadinessScorecard(rootInput, extraFindings = []) {
9
+ const root = resolveRepoRoot(rootInput);
10
+ const result = await scan({ cwd: root });
11
+ const categories = [];
12
+ const hasReadme = await exists(path.join(root, "README.md"));
13
+ categories.push({
14
+ id: "documentation",
15
+ title: "Documentation quality",
16
+ score: hasReadme ? 70 : 30,
17
+ evidence: [
18
+ {
19
+ kind: hasReadme ? "verified" : "unknown",
20
+ path: "README.md",
21
+ detail: hasReadme ? "README present" : "README missing",
22
+ },
23
+ ],
24
+ gaps: hasReadme ? [] : ["Add a README with agent setup guidance"],
25
+ recommendations: ["Keep AGENTS.md/CLAUDE.md aligned with Safety scan findings"],
26
+ });
27
+ const hasTests = (await exists(path.join(root, "tests"))) || (await exists(path.join(root, "test")));
28
+ categories.push({
29
+ id: "tests",
30
+ title: "Test quality",
31
+ score: hasTests ? 75 : 25,
32
+ evidence: [
33
+ {
34
+ kind: hasTests ? "verified" : "unknown",
35
+ detail: hasTests ? "tests/ or test/ directory present" : "no tests directory",
36
+ },
37
+ ],
38
+ gaps: hasTests ? [] : ["Add automated tests before enabling autonomous agents"],
39
+ recommendations: ["Wire CI to fail on critical Safety findings"],
40
+ });
41
+ const securityFindings = result.findings.filter((f) => f.category === "security");
42
+ const secScore = Math.max(0, 100 - securityFindings.length * 15);
43
+ categories.push({
44
+ id: "security",
45
+ title: "Security controls",
46
+ score: secScore,
47
+ evidence: securityFindings.slice(0, 5).map((f) => {
48
+ const ref = {
49
+ kind: "verified",
50
+ detail: f.title,
51
+ };
52
+ if (f.evidence?.path)
53
+ ref.path = f.evidence.path;
54
+ return ref;
55
+ }),
56
+ gaps: securityFindings.map((f) => f.title),
57
+ recommendations: ["Run agentdoctor secrets --json for opt-in content hygiene"],
58
+ });
59
+ const criticalExtra = extraFindings.filter((f) => f.severity === "critical" || f.severity === "high");
60
+ categories.push({
61
+ id: "agent-policies",
62
+ title: "Agent policies & context security",
63
+ score: Math.max(0, 100 - criticalExtra.length * 20),
64
+ evidence: criticalExtra.slice(0, 5).map((f) => {
65
+ const ref = {
66
+ kind: "inferred",
67
+ detail: f.title,
68
+ };
69
+ if (f.evidence[0]?.path)
70
+ ref.path = f.evidence[0].path;
71
+ return ref;
72
+ }),
73
+ gaps: criticalExtra.map((f) => f.title),
74
+ recommendations: [
75
+ "Configure .agentdoctor/platform/firewall-policy.json (Action Policy Evaluator)",
76
+ ],
77
+ });
78
+ const overallNote = "Scores are category evidence summaries — not a single unexplained rating. " +
79
+ "Improve gaps before expanding agent autonomy.";
80
+ return {
81
+ root,
82
+ categories,
83
+ overallNote,
84
+ limitations: [
85
+ "CI/CD maturity and observability are only partially assessed in this MVP",
86
+ "Ownership metadata requires CODEOWNERS for higher confidence",
87
+ ],
88
+ };
89
+ }
90
+ async function exists(p) {
91
+ try {
92
+ await fs.access(p);
93
+ return true;
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
@@ -0,0 +1,20 @@
1
+ import type { RepositoryGraph } from "../types.js";
2
+ export interface RefactorImpact {
3
+ root: string;
4
+ symbol: string;
5
+ affectedFiles: string[];
6
+ dependencyPaths: string[];
7
+ risk: "low" | "medium" | "high";
8
+ requiredTests: string[];
9
+ breakingChangeRisk: string;
10
+ limitations: string[];
11
+ }
12
+ /**
13
+ * Module K — Graph-aware refactoring analysis (rename impact MVP).
14
+ * Does not apply refactors.
15
+ */
16
+ export declare function analyzeRenameImpact(options: {
17
+ root: string;
18
+ symbol: string;
19
+ graph: RepositoryGraph;
20
+ }): Promise<RefactorImpact>;