@praneeth_54/agentdoctor 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/CHANGELOG.md +119 -5
  2. package/README.md +165 -332
  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/cli/commands/brain.d.ts +12 -0
  24. package/dist/cli/commands/brain.js +162 -0
  25. package/dist/cli/commands/complete.d.ts +23 -0
  26. package/dist/cli/commands/complete.js +232 -0
  27. package/dist/cli/commands/explain.js +1 -1
  28. package/dist/cli/commands/fix.js +22 -2
  29. package/dist/cli/commands/mcp.d.ts +8 -0
  30. package/dist/cli/commands/mcp.js +14 -0
  31. package/dist/cli/commands/platform.d.ts +15 -0
  32. package/dist/cli/commands/platform.js +191 -0
  33. package/dist/cli/commands/v2.d.ts +55 -0
  34. package/dist/cli/commands/v2.js +278 -0
  35. package/dist/cli/program.js +809 -2
  36. package/dist/constants.d.ts +3 -2
  37. package/dist/constants.js +5 -1
  38. package/dist/contracts/adapters.d.ts +7 -0
  39. package/dist/contracts/adapters.js +60 -0
  40. package/dist/contracts/index.d.ts +162 -0
  41. package/dist/contracts/index.js +18 -0
  42. package/dist/core/baseline/store.d.ts +48 -0
  43. package/dist/core/baseline/store.js +163 -0
  44. package/dist/core/brain-cli/service.d.ts +26 -0
  45. package/dist/core/brain-cli/service.js +175 -0
  46. package/dist/core/brain-product/init.d.ts +45 -0
  47. package/dist/core/brain-product/init.js +232 -0
  48. package/dist/core/changes/analyze.d.ts +41 -0
  49. package/dist/core/changes/analyze.js +223 -0
  50. package/dist/core/context-health/analyze.d.ts +15 -0
  51. package/dist/core/context-health/analyze.js +234 -0
  52. package/dist/core/fix/apply.d.ts +21 -4
  53. package/dist/core/fix/apply.js +118 -18
  54. package/dist/core/fix/backup.d.ts +30 -0
  55. package/dist/core/fix/backup.js +193 -0
  56. package/dist/core/fix/plan.d.ts +6 -3
  57. package/dist/core/fix/plan.js +113 -9
  58. package/dist/core/fix/render.d.ts +2 -0
  59. package/dist/core/fix/render.js +36 -21
  60. package/dist/core/fix/run.js +5 -1
  61. package/dist/core/fix/safe-target.d.ts +14 -0
  62. package/dist/core/fix/safe-target.js +81 -0
  63. package/dist/core/fix/types.d.ts +12 -1
  64. package/dist/core/fix/types.js +2 -0
  65. package/dist/core/fix/writers/claude-settings.js +3 -1
  66. package/dist/core/fix/writers/codex-config.d.ts +3 -0
  67. package/dist/core/fix/writers/codex-config.js +24 -3
  68. package/dist/core/fix/writers/cursorignore.js +3 -1
  69. package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
  70. package/dist/core/fix/writers/simple-ignore.js +68 -0
  71. package/dist/core/monorepo/detect.d.ts +13 -0
  72. package/dist/core/monorepo/detect.js +121 -0
  73. package/dist/core/rules/build-context.js +22 -3
  74. package/dist/core/rules/context/generated-directory.js +11 -1
  75. package/dist/core/rules/context/large-instruction-file.js +6 -0
  76. package/dist/core/rules/context/large-log-file.js +16 -1
  77. package/dist/core/rules/ignore.d.ts +11 -1
  78. package/dist/core/rules/ignore.js +22 -1
  79. package/dist/core/rules/instructions/empty-instructions.js +7 -0
  80. package/dist/core/rules/security/env-file-exposure.js +23 -2
  81. package/dist/core/rules/security/private-key-file.js +1 -1
  82. package/dist/core/scanner/scan.js +1 -1
  83. package/dist/core/schemas/validate.d.ts +6 -0
  84. package/dist/core/schemas/validate.js +62 -0
  85. package/dist/core/scoring/compute-scores.d.ts +1 -1
  86. package/dist/core/scoring/compute-scores.js +5 -1
  87. package/dist/core/scoring/placeholder.d.ts +1 -1
  88. package/dist/core/scoring/placeholder.js +5 -1
  89. package/dist/core/secrets/scan.d.ts +23 -0
  90. package/dist/core/secrets/scan.js +172 -0
  91. package/dist/dashboard/server.d.ts +24 -0
  92. package/dist/dashboard/server.js +386 -0
  93. package/dist/enforcement/runner.d.ts +23 -0
  94. package/dist/enforcement/runner.js +56 -0
  95. package/dist/index.d.ts +13 -0
  96. package/dist/index.js +12 -0
  97. package/dist/integrations/github/pr-analyze.d.ts +28 -0
  98. package/dist/integrations/github/pr-analyze.js +98 -0
  99. package/dist/integrations/local-ai/provider.d.ts +45 -0
  100. package/dist/integrations/local-ai/provider.js +104 -0
  101. package/dist/intelligence/git/analyze.d.ts +34 -0
  102. package/dist/intelligence/git/analyze.js +136 -0
  103. package/dist/intelligence/graph/build.d.ts +13 -0
  104. package/dist/intelligence/graph/build.js +215 -0
  105. package/dist/knowledge/store.d.ts +25 -0
  106. package/dist/knowledge/store.js +91 -0
  107. package/dist/mcp/agentdoctor/server.d.ts +16 -0
  108. package/dist/mcp/agentdoctor/server.js +67 -0
  109. package/dist/mcp/brain/session.js +7 -7
  110. package/dist/mcp/intelligence/handlers.d.ts +11 -0
  111. package/dist/mcp/intelligence/handlers.js +175 -0
  112. package/dist/mcp/intelligence/path-safety.d.ts +11 -0
  113. package/dist/mcp/intelligence/path-safety.js +82 -0
  114. package/dist/mcp/intelligence/registry.d.ts +8 -0
  115. package/dist/mcp/intelligence/registry.js +160 -0
  116. package/dist/ops/health.d.ts +18 -0
  117. package/dist/ops/health.js +51 -0
  118. package/dist/platform/ai-quality/analyze.d.ts +9 -0
  119. package/dist/platform/ai-quality/analyze.js +65 -0
  120. package/dist/platform/architecture/drift.d.ts +17 -0
  121. package/dist/platform/architecture/drift.js +74 -0
  122. package/dist/platform/auth/local.d.ts +15 -0
  123. package/dist/platform/auth/local.js +58 -0
  124. package/dist/platform/context-security/analyze.d.ts +6 -0
  125. package/dist/platform/context-security/analyze.js +97 -0
  126. package/dist/platform/firewall/evaluate.d.ts +54 -0
  127. package/dist/platform/firewall/evaluate.js +308 -0
  128. package/dist/platform/graph/build.d.ts +6 -0
  129. package/dist/platform/graph/build.js +193 -0
  130. package/dist/platform/health/analyze.d.ts +7 -0
  131. package/dist/platform/health/analyze.js +186 -0
  132. package/dist/platform/index.d.ts +27 -0
  133. package/dist/platform/index.js +87 -0
  134. package/dist/platform/knowledge/analyze.d.ts +20 -0
  135. package/dist/platform/knowledge/analyze.js +96 -0
  136. package/dist/platform/provenance/build.d.ts +44 -0
  137. package/dist/platform/provenance/build.js +65 -0
  138. package/dist/platform/readiness/scorecard.d.ts +5 -0
  139. package/dist/platform/readiness/scorecard.js +98 -0
  140. package/dist/platform/refactor/impact.d.ts +20 -0
  141. package/dist/platform/refactor/impact.js +60 -0
  142. package/dist/platform/reports/export.d.ts +12 -0
  143. package/dist/platform/reports/export.js +92 -0
  144. package/dist/platform/security/redact.d.ts +15 -0
  145. package/dist/platform/security/redact.js +87 -0
  146. package/dist/platform/sessions/store.d.ts +32 -0
  147. package/dist/platform/sessions/store.js +97 -0
  148. package/dist/platform/store.d.ts +6 -0
  149. package/dist/platform/store.js +57 -0
  150. package/dist/platform/test-impact/analyze.d.ts +32 -0
  151. package/dist/platform/test-impact/analyze.js +164 -0
  152. package/dist/platform/time-machine/compare.d.ts +17 -0
  153. package/dist/platform/time-machine/compare.js +62 -0
  154. package/dist/platform/tokens/plan.d.ts +28 -0
  155. package/dist/platform/tokens/plan.js +89 -0
  156. package/dist/platform/types.d.ts +111 -0
  157. package/dist/platform/types.js +5 -0
  158. package/dist/plugins/runtime.d.ts +36 -0
  159. package/dist/plugins/runtime.js +101 -0
  160. package/dist/plugins/sdk.d.ts +29 -0
  161. package/dist/plugins/sdk.js +114 -0
  162. package/dist/policy/packs.d.ts +9 -0
  163. package/dist/policy/packs.js +59 -0
  164. package/dist/reporters/terminal/report.js +38 -5
  165. package/dist/storage/provider.d.ts +35 -0
  166. package/dist/storage/provider.js +103 -0
  167. package/dist/team/auth.d.ts +35 -0
  168. package/dist/team/auth.js +70 -0
  169. package/dist/types/index.d.ts +5 -1
  170. package/package.json +14 -7
package/dist/index.js CHANGED
@@ -1,3 +1,15 @@
1
+ export { CONTRACTS_VERSION, DEFAULT_FEATURE_FLAGS } from "./contracts/index.js";
2
+ export { buildIntelligenceGraph } from "./intelligence/graph/build.js";
3
+ export { analyzeGitIntelligence } from "./intelligence/git/analyze.js";
4
+ export { buildC4Views } from "./architecture/c4.js";
5
+ export { createKnowledgeRecord, listKnowledge, transitionKnowledge, retrieveAuthoritative, } from "./knowledge/store.js";
6
+ export { FilesystemStorageProvider, MemoryStorageProvider } from "./storage/provider.js";
7
+ export { runControlledCommand } from "./enforcement/runner.js";
8
+ export { registerLocalDevUser, authenticateLocalDev, authorize } from "./team/auth.js";
9
+ export { runProjectInit, listProposals, reviewProposal } from "./core/brain-product/init.js";
10
+ export { collectOpsHealth } from "./ops/health.js";
11
+ export { listPolicyPacks, getPolicyPack } from "./policy/packs.js";
12
+ export { listIntelligenceMcpTools, INTELLIGENCE_MCP_TOOL_NAMES, } from "./mcp/intelligence/registry.js";
1
13
  export { scan } from "./core/scanner/scan.js";
2
14
  export { verify } from "./core/verify/verify.js";
3
15
  export { compareFindings } from "./core/verify/compare.js";
@@ -0,0 +1,28 @@
1
+ export interface PrReviewFinding {
2
+ severity: "critical" | "warning" | "info";
3
+ title: string;
4
+ message: string;
5
+ file?: string;
6
+ line?: number;
7
+ confidence: "high" | "medium" | "low";
8
+ source: "scan" | "context-health" | "changes";
9
+ }
10
+ export interface PrReviewReport {
11
+ root: string;
12
+ mode: "local" | "github-dry-run";
13
+ baseRef: string | null;
14
+ headRef: string | null;
15
+ findings: PrReviewFinding[];
16
+ commentMarkdown: string;
17
+ limitations: string[];
18
+ posted: false;
19
+ }
20
+ /**
21
+ * Local PR review. Never posts to GitHub unless a future explicit command is added.
22
+ * GitHub token is unused; dry-run is the only supported remote mode.
23
+ */
24
+ export declare function analyzePullRequest(options: {
25
+ root: string;
26
+ baseRef?: string;
27
+ dryRun?: boolean;
28
+ }): Promise<PrReviewReport>;
@@ -0,0 +1,98 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { analyzeChanges } from "../../core/changes/analyze.js";
3
+ import { analyzeContextHealth } from "../../core/context-health/analyze.js";
4
+ import { resolveRepoRoot } from "../../utils/path.js";
5
+ import { scan } from "../../core/scanner/scan.js";
6
+ function redactSecretsInText(text) {
7
+ return text
8
+ .replace(/\bAKIA[0-9A-Z]{16}\b/g, "[REDACTED]")
9
+ .replace(/\bghp_[A-Za-z0-9]{36}\b/g, "[REDACTED]")
10
+ .replace(/-----BEGIN [^-]+PRIVATE KEY-----/g, "[REDACTED PRIVATE KEY]");
11
+ }
12
+ function buildComment(findings) {
13
+ const lines = [
14
+ "## AgentDoctor PR review (local)",
15
+ "",
16
+ "_Generated locally. Secrets are redacted. Nothing was posted to GitHub._",
17
+ "",
18
+ ];
19
+ if (findings.length === 0) {
20
+ lines.push("No actionable findings in local analysis.");
21
+ return lines.join("\n");
22
+ }
23
+ for (const f of findings) {
24
+ const loc = f.file ? ` \`${f.file}${f.line ? `:${f.line}` : ""}\`` : "";
25
+ lines.push(`- **${f.severity}** (${f.confidence}) ${redactSecretsInText(f.title)}${loc}: ${redactSecretsInText(f.message)}`);
26
+ }
27
+ return `${lines.join("\n")}\n`;
28
+ }
29
+ /**
30
+ * Local PR review. Never posts to GitHub unless a future explicit command is added.
31
+ * GitHub token is unused; dry-run is the only supported remote mode.
32
+ */
33
+ export async function analyzePullRequest(options) {
34
+ const root = resolveRepoRoot(options.root);
35
+ const limitations = [
36
+ "GitHub comment posting is disabled; use --dry-run / local mode only",
37
+ "Optional GitHub API integration requires explicit future invocation with a token",
38
+ ];
39
+ const baseRef = options.baseRef ?? null;
40
+ const changes = await analyzeChanges({
41
+ root,
42
+ ...(baseRef ? { since: baseRef } : {}),
43
+ });
44
+ const context = await analyzeContextHealth(root);
45
+ const scanResult = await scan({ cwd: root });
46
+ const findings = [];
47
+ for (const f of scanResult.findings.filter((x) => x.severity === "critical" || x.severity === "warning")) {
48
+ findings.push({
49
+ severity: f.severity,
50
+ title: f.title,
51
+ message: f.message,
52
+ ...(f.evidence?.path ? { file: f.evidence.path } : {}),
53
+ ...(typeof f.evidence?.line === "number" ? { line: f.evidence.line } : {}),
54
+ confidence: "high",
55
+ source: "scan",
56
+ });
57
+ }
58
+ for (const c of context.conflicts) {
59
+ findings.push({
60
+ severity: c.severity === "warning" ? "warning" : "info",
61
+ title: c.kind,
62
+ message: c.message,
63
+ ...(c.files[0] ? { file: c.files[0] } : {}),
64
+ confidence: "medium",
65
+ source: "context-health",
66
+ });
67
+ }
68
+ if (changes.gitAvailable && changes.files.length > 0) {
69
+ findings.push({
70
+ severity: "info",
71
+ title: "changed-files",
72
+ message: `${changes.files.length} changed file(s) in working tree / range`,
73
+ confidence: "high",
74
+ source: "changes",
75
+ });
76
+ }
77
+ else if (!changes.gitAvailable) {
78
+ limitations.push(...changes.limitations);
79
+ }
80
+ let headRef = changes.head;
81
+ if (!headRef) {
82
+ const probe = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
83
+ cwd: root,
84
+ encoding: "utf8",
85
+ });
86
+ headRef = probe.status === 0 ? probe.stdout.trim() : null;
87
+ }
88
+ return {
89
+ root,
90
+ mode: options.dryRun === false ? "local" : "github-dry-run",
91
+ baseRef,
92
+ headRef,
93
+ findings,
94
+ commentMarkdown: buildComment(findings),
95
+ limitations,
96
+ posted: false,
97
+ };
98
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Optional local AI integration.
3
+ * Core AgentDoctor never requires a model. Providers are opt-in and must redact
4
+ * sensitive content before any optional call. AI output must not bypass Safe Fix.
5
+ */
6
+ export type LocalAiProviderId = "none" | "mock" | "ollama";
7
+ export interface LocalAiRequest {
8
+ prompt: string;
9
+ /** Already-redacted context snippets only */
10
+ context: string[];
11
+ maxTokens?: number;
12
+ timeoutMs?: number;
13
+ }
14
+ export interface LocalAiResponse {
15
+ provider: LocalAiProviderId;
16
+ text: string;
17
+ /** Always true for AI-generated content so callers can label output */
18
+ aiGenerated: true;
19
+ truncated: boolean;
20
+ error?: string;
21
+ }
22
+ export interface LocalAiProvider {
23
+ id: LocalAiProviderId;
24
+ generate(request: LocalAiRequest): Promise<LocalAiResponse>;
25
+ }
26
+ export declare function redactForModel(text: string): string;
27
+ export declare class NoneAiProvider implements LocalAiProvider {
28
+ id: LocalAiProviderId;
29
+ generate(_request: LocalAiRequest): Promise<LocalAiResponse>;
30
+ }
31
+ export declare class MockAiProvider implements LocalAiProvider {
32
+ id: LocalAiProviderId;
33
+ generate(request: LocalAiRequest): Promise<LocalAiResponse>;
34
+ }
35
+ /**
36
+ * Ollama provider stub: documents integration without requiring ollama installed.
37
+ * Calls fail closed with a clear error when the daemon is unavailable.
38
+ */
39
+ export declare class OllamaAiProvider implements LocalAiProvider {
40
+ private readonly endpoint;
41
+ id: LocalAiProviderId;
42
+ constructor(endpoint?: string);
43
+ generate(request: LocalAiRequest): Promise<LocalAiResponse>;
44
+ }
45
+ export declare function createLocalAiProvider(id: LocalAiProviderId): LocalAiProvider;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Optional local AI integration.
3
+ * Core AgentDoctor never requires a model. Providers are opt-in and must redact
4
+ * sensitive content before any optional call. AI output must not bypass Safe Fix.
5
+ */
6
+ const REDACT = /\b(AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|-----BEGIN [^-]+PRIVATE KEY-----)/g;
7
+ export function redactForModel(text) {
8
+ return text.replace(REDACT, "[REDACTED]");
9
+ }
10
+ export class NoneAiProvider {
11
+ id = "none";
12
+ async generate(_request) {
13
+ return {
14
+ provider: "none",
15
+ text: "",
16
+ aiGenerated: true,
17
+ truncated: false,
18
+ error: "Local AI disabled (deterministic mode)",
19
+ };
20
+ }
21
+ }
22
+ export class MockAiProvider {
23
+ id = "mock";
24
+ async generate(request) {
25
+ const joined = redactForModel([request.prompt, ...request.context].join("\n"));
26
+ const max = request.maxTokens ?? 256;
27
+ const text = `[AI-GENERATED mock] Summary of ${joined.length} redacted chars.`.slice(0, max);
28
+ return {
29
+ provider: "mock",
30
+ text,
31
+ aiGenerated: true,
32
+ truncated: joined.length > max,
33
+ };
34
+ }
35
+ }
36
+ /**
37
+ * Ollama provider stub: documents integration without requiring ollama installed.
38
+ * Calls fail closed with a clear error when the daemon is unavailable.
39
+ */
40
+ export class OllamaAiProvider {
41
+ endpoint;
42
+ id = "ollama";
43
+ constructor(endpoint = "http://127.0.0.1:11434") {
44
+ this.endpoint = endpoint;
45
+ }
46
+ async generate(request) {
47
+ const timeoutMs = request.timeoutMs ?? 5_000;
48
+ const controller = new AbortController();
49
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
50
+ try {
51
+ const prompt = redactForModel(`${request.prompt}\n\n${request.context.map(redactForModel).join("\n")}`.slice(0, 8_000));
52
+ const res = await fetch(`${this.endpoint}/api/generate`, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify({
56
+ model: "llama3.2",
57
+ prompt,
58
+ stream: false,
59
+ options: { num_predict: request.maxTokens ?? 256 },
60
+ }),
61
+ signal: controller.signal,
62
+ });
63
+ if (!res.ok) {
64
+ return {
65
+ provider: "ollama",
66
+ text: "",
67
+ aiGenerated: true,
68
+ truncated: false,
69
+ error: `ollama HTTP ${res.status}`,
70
+ };
71
+ }
72
+ const data = (await res.json());
73
+ return {
74
+ provider: "ollama",
75
+ text: `[AI-GENERATED] ${String(data.response ?? "").trim()}`,
76
+ aiGenerated: true,
77
+ truncated: false,
78
+ };
79
+ }
80
+ catch (error) {
81
+ return {
82
+ provider: "ollama",
83
+ text: "",
84
+ aiGenerated: true,
85
+ truncated: false,
86
+ error: error instanceof Error ? error.message : String(error),
87
+ };
88
+ }
89
+ finally {
90
+ clearTimeout(timer);
91
+ }
92
+ }
93
+ }
94
+ export function createLocalAiProvider(id) {
95
+ switch (id) {
96
+ case "mock":
97
+ return new MockAiProvider();
98
+ case "ollama":
99
+ return new OllamaAiProvider();
100
+ case "none":
101
+ default:
102
+ return new NoneAiProvider();
103
+ }
104
+ }
@@ -0,0 +1,34 @@
1
+ export interface HotspotFile {
2
+ path: string;
3
+ commits: number;
4
+ authors: string[];
5
+ busFactor: number;
6
+ method: string;
7
+ }
8
+ export interface GitIntelligenceReport {
9
+ root: string;
10
+ gitAvailable: boolean;
11
+ hotspots: HotspotFile[];
12
+ coChanges: Array<{
13
+ a: string;
14
+ b: string;
15
+ together: number;
16
+ method: string;
17
+ }>;
18
+ limitations: string[];
19
+ }
20
+ /**
21
+ * Git engineering intelligence — methods disclosed on every metric.
22
+ */
23
+ export declare function analyzeGitIntelligence(rootInput: string): Promise<GitIntelligenceReport>;
24
+ export declare function deadCodeCategory(options: {
25
+ exported: boolean;
26
+ referenced: boolean;
27
+ frameworkEntry: boolean;
28
+ dynamicHint: boolean;
29
+ }): {
30
+ category: "confirmed-unreachable" | "probably-unused" | "possibly-unused" | "public-api" | "framework-discovered" | "reflection-sensitive" | "dynamic-reference-risk" | "unknown";
31
+ confidence: number;
32
+ explanation: string;
33
+ };
34
+ export declare function stableMetricId(kind: string, path: string): string;
@@ -0,0 +1,136 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import { resolveRepoRoot } from "../../utils/path.js";
4
+ function git(root, args) {
5
+ const r = spawnSync("git", args, {
6
+ cwd: root,
7
+ encoding: "utf8",
8
+ maxBuffer: 16 * 1024 * 1024,
9
+ });
10
+ return { ok: r.status === 0, out: typeof r.stdout === "string" ? r.stdout : "" };
11
+ }
12
+ /**
13
+ * Git engineering intelligence — methods disclosed on every metric.
14
+ */
15
+ export async function analyzeGitIntelligence(rootInput) {
16
+ const root = resolveRepoRoot(rootInput);
17
+ const probe = git(root, ["rev-parse", "--is-inside-work-tree"]);
18
+ if (!probe.ok || probe.out.trim() !== "true") {
19
+ return {
20
+ root,
21
+ gitAvailable: false,
22
+ hotspots: [],
23
+ coChanges: [],
24
+ limitations: ["Requires a git repository"],
25
+ };
26
+ }
27
+ const log = git(root, ["log", "--name-only", "--pretty=format:===%an", "-n", "200"]);
28
+ const authorsByFile = new Map();
29
+ const commitsByFile = new Map();
30
+ const commitFiles = [];
31
+ let currentAuthor = "unknown";
32
+ let bucket = [];
33
+ for (const line of log.out.split(/\r?\n/)) {
34
+ if (line.startsWith("===")) {
35
+ if (bucket.length)
36
+ commitFiles.push(bucket);
37
+ bucket = [];
38
+ currentAuthor = line.slice(3).trim() || "unknown";
39
+ continue;
40
+ }
41
+ const file = line.trim();
42
+ if (!file || file.includes("\0"))
43
+ continue;
44
+ bucket.push(file);
45
+ commitsByFile.set(file, (commitsByFile.get(file) ?? 0) + 1);
46
+ if (!authorsByFile.has(file))
47
+ authorsByFile.set(file, new Set());
48
+ authorsByFile.get(file).add(currentAuthor);
49
+ }
50
+ if (bucket.length)
51
+ commitFiles.push(bucket);
52
+ const hotspots = [...commitsByFile.entries()]
53
+ .map(([path, commits]) => {
54
+ const authors = [...(authorsByFile.get(path) ?? [])].sort();
55
+ return {
56
+ path,
57
+ commits,
58
+ authors,
59
+ busFactor: authors.length,
60
+ method: "count of file appearances in last ≤200 commits; busFactor=distinct authors in that window",
61
+ };
62
+ })
63
+ .sort((a, b) => b.commits - a.commits || a.path.localeCompare(b.path))
64
+ .slice(0, 50);
65
+ const pairCounts = new Map();
66
+ for (const files of commitFiles) {
67
+ const uniq = [...new Set(files)].sort();
68
+ for (let i = 0; i < uniq.length; i += 1) {
69
+ for (let j = i + 1; j < Math.min(uniq.length, i + 8); j += 1) {
70
+ const key = `${uniq[i]}||${uniq[j]}`;
71
+ pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);
72
+ }
73
+ }
74
+ }
75
+ const coChanges = [...pairCounts.entries()]
76
+ .filter(([, n]) => n >= 2)
77
+ .map(([key, together]) => {
78
+ const [a, b] = key.split("||");
79
+ return {
80
+ a: a,
81
+ b: b,
82
+ together,
83
+ method: "pair co-occurrence in same commit (window ≤200; capped pairs per commit)",
84
+ };
85
+ })
86
+ .sort((x, y) => y.together - x.together)
87
+ .slice(0, 40);
88
+ return {
89
+ root,
90
+ gitAvailable: true,
91
+ hotspots,
92
+ coChanges,
93
+ limitations: [
94
+ "Hotspots use recent commit window only (not full history)",
95
+ "Bus factor is author cardinality in window — not organizational coverage",
96
+ "Bug-fix history is not classified in this MVP",
97
+ ],
98
+ };
99
+ }
100
+ export function deadCodeCategory(options) {
101
+ if (options.frameworkEntry) {
102
+ return {
103
+ category: "framework-discovered",
104
+ confidence: 0.4,
105
+ explanation: "Looks like a framework entry — do not mark dead without framework adapter proof",
106
+ };
107
+ }
108
+ if (options.dynamicHint) {
109
+ return {
110
+ category: "dynamic-reference-risk",
111
+ confidence: 0.3,
112
+ explanation: "Dynamic/reflection-style reference risk",
113
+ };
114
+ }
115
+ if (options.exported && !options.referenced) {
116
+ return {
117
+ category: "public-api",
118
+ confidence: 0.55,
119
+ explanation: "Exported but no static references observed — may be public API",
120
+ };
121
+ }
122
+ if (!options.exported && !options.referenced) {
123
+ return {
124
+ category: "possibly-unused",
125
+ confidence: 0.5,
126
+ explanation: "No static references; not confirmed unreachable across all loaders",
127
+ };
128
+ }
129
+ if (!options.exported && options.referenced) {
130
+ return { category: "unknown", confidence: 0.2, explanation: "Referenced locally" };
131
+ }
132
+ return { category: "unknown", confidence: 0.2, explanation: "Insufficient evidence" };
133
+ }
134
+ export function stableMetricId(kind, path) {
135
+ return `git_${createHash("sha1").update(`${kind}:${path}`).digest("hex").slice(0, 12)}`;
136
+ }
@@ -0,0 +1,13 @@
1
+ import type { RepositoryGraph } from "../../platform/types.js";
2
+ export type GraphBuilderMode = "regex" | "typescript-ast" | "auto";
3
+ /**
4
+ * TypeScript compiler API based graph enrichment.
5
+ * Falls back to regex graph when mode=regex or on failure.
6
+ */
7
+ export declare function buildIntelligenceGraph(options: {
8
+ root: string;
9
+ mode?: GraphBuilderMode;
10
+ }): Promise<RepositoryGraph & {
11
+ builder: GraphBuilderMode;
12
+ astFilesParsed: number;
13
+ }>;