@praneeth_54/agentdoctor 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +57 -3
  2. package/README.md +489 -290
  3. package/dist/agent/chat/deterministic.d.ts +6 -0
  4. package/dist/agent/chat/deterministic.js +180 -0
  5. package/dist/agent/chat/service.js +8 -14
  6. package/dist/agent/index.d.ts +2 -0
  7. package/dist/agent/index.js +1 -0
  8. package/dist/agent/loop.d.ts +2 -0
  9. package/dist/agent/loop.js +31 -0
  10. package/dist/agent/roles.d.ts +12 -0
  11. package/dist/agent/roles.js +138 -0
  12. package/dist/agent/runtime.d.ts +21 -5
  13. package/dist/agent/runtime.js +141 -28
  14. package/dist/agent/tools/execute.d.ts +2 -0
  15. package/dist/agent/tools/execute.js +10 -0
  16. package/dist/ai/config.js +1 -0
  17. package/dist/ai/redact.d.ts +1 -0
  18. package/dist/ai/redact.js +47 -7
  19. package/dist/ai/types.d.ts +1 -1
  20. package/dist/cli/commands/agent.d.ts +2 -0
  21. package/dist/cli/commands/agent.js +19 -8
  22. package/dist/cli/commands/chat.js +2 -4
  23. package/dist/cli/commands/product.d.ts +19 -0
  24. package/dist/cli/commands/product.js +147 -0
  25. package/dist/cli/commands/start.d.ts +15 -0
  26. package/dist/cli/commands/start.js +80 -0
  27. package/dist/cli/program.js +141 -0
  28. package/dist/constants.d.ts +1 -1
  29. package/dist/constants.js +1 -1
  30. package/dist/dashboard/server.js +255 -53
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.js +2 -0
  33. package/dist/intelligence/graph/build.js +36 -8
  34. package/dist/intelligence/resolve/imports.js +1 -1
  35. package/dist/languages/dart.d.ts +10 -0
  36. package/dist/languages/dart.js +99 -0
  37. package/dist/languages/go.d.ts +13 -7
  38. package/dist/languages/go.js +84 -24
  39. package/dist/languages/index.d.ts +5 -1
  40. package/dist/languages/index.js +13 -36
  41. package/dist/languages/java.d.ts +10 -0
  42. package/dist/languages/java.js +80 -0
  43. package/dist/languages/kotlin.d.ts +10 -0
  44. package/dist/languages/kotlin.js +85 -0
  45. package/dist/languages/rust.d.ts +10 -0
  46. package/dist/languages/rust.js +93 -0
  47. package/dist/languages/types.d.ts +4 -3
  48. package/dist/mcp/agent/registry.d.ts +1 -1
  49. package/dist/mcp/agent/registry.js +109 -23
  50. package/dist/mcp/intelligence/handlers.d.ts +3 -0
  51. package/dist/mcp/intelligence/handlers.js +28 -0
  52. package/dist/mcp/intelligence/registry.d.ts +1 -1
  53. package/dist/mcp/intelligence/registry.js +30 -1
  54. package/dist/product/api/doctor.d.ts +14 -0
  55. package/dist/product/api/doctor.js +185 -0
  56. package/dist/product/api/openapi.d.ts +7 -0
  57. package/dist/product/api/openapi.js +122 -0
  58. package/dist/product/approval/model.d.ts +30 -0
  59. package/dist/product/approval/model.js +64 -0
  60. package/dist/product/approval/session.d.ts +49 -0
  61. package/dist/product/approval/session.js +134 -0
  62. package/dist/product/database/doctor.d.ts +30 -0
  63. package/dist/product/database/doctor.js +184 -0
  64. package/dist/product/decisions/ledger.d.ts +24 -0
  65. package/dist/product/decisions/ledger.js +110 -0
  66. package/dist/product/deps/analyze.d.ts +46 -0
  67. package/dist/product/deps/analyze.js +137 -0
  68. package/dist/product/deps/lockfiles.d.ts +25 -0
  69. package/dist/product/deps/lockfiles.js +200 -0
  70. package/dist/product/discovery/roots.d.ts +42 -0
  71. package/dist/product/discovery/roots.js +215 -0
  72. package/dist/product/dna/build.d.ts +50 -0
  73. package/dist/product/dna/build.js +255 -0
  74. package/dist/product/eval/lab.d.ts +17 -0
  75. package/dist/product/eval/lab.js +218 -0
  76. package/dist/product/events/doctor.d.ts +21 -0
  77. package/dist/product/events/doctor.js +147 -0
  78. package/dist/product/evidence-scan.d.ts +12 -0
  79. package/dist/product/evidence-scan.js +46 -0
  80. package/dist/product/evolution/timeline.d.ts +29 -0
  81. package/dist/product/evolution/timeline.js +123 -0
  82. package/dist/product/features/intelligence.d.ts +23 -0
  83. package/dist/product/features/intelligence.js +158 -0
  84. package/dist/product/forensic/mode.d.ts +25 -0
  85. package/dist/product/forensic/mode.js +70 -0
  86. package/dist/product/graph/enrich-languages.d.ts +20 -0
  87. package/dist/product/graph/enrich-languages.js +193 -0
  88. package/dist/product/health/code-health.d.ts +20 -0
  89. package/dist/product/health/code-health.js +149 -0
  90. package/dist/product/index.d.ts +69 -0
  91. package/dist/product/index.js +35 -0
  92. package/dist/product/ledger/change-ledger.d.ts +23 -0
  93. package/dist/product/ledger/change-ledger.js +64 -0
  94. package/dist/product/map/software-map.d.ts +17 -0
  95. package/dist/product/map/software-map.js +95 -0
  96. package/dist/product/memory/institutional.d.ts +16 -0
  97. package/dist/product/memory/institutional.js +87 -0
  98. package/dist/product/ops/incident.d.ts +20 -0
  99. package/dist/product/ops/incident.js +61 -0
  100. package/dist/product/ops/infra.d.ts +15 -0
  101. package/dist/product/ops/infra.js +112 -0
  102. package/dist/product/org/model.d.ts +34 -0
  103. package/dist/product/org/model.js +195 -0
  104. package/dist/product/privacy/doctor.d.ts +13 -0
  105. package/dist/product/privacy/doctor.js +90 -0
  106. package/dist/product/requirements/trace.d.ts +21 -0
  107. package/dist/product/requirements/trace.js +166 -0
  108. package/dist/product/search/index.d.ts +29 -0
  109. package/dist/product/search/index.js +116 -0
  110. package/dist/product/search/software-search.d.ts +19 -0
  111. package/dist/product/search/software-search.js +100 -0
  112. package/dist/product/security/doctor.d.ts +23 -0
  113. package/dist/product/security/doctor.js +124 -0
  114. package/dist/product/self/diagnose.d.ts +15 -0
  115. package/dist/product/self/diagnose.js +82 -0
  116. package/dist/product/techdebt/roadmap.d.ts +20 -0
  117. package/dist/product/techdebt/roadmap.js +118 -0
  118. package/dist/product/testbrain/analyze.d.ts +29 -0
  119. package/dist/product/testbrain/analyze.js +129 -0
  120. package/dist/product/truth.d.ts +12 -0
  121. package/dist/product/truth.js +30 -0
  122. package/dist/product/twin/digital-twin.d.ts +23 -0
  123. package/dist/product/twin/digital-twin.js +52 -0
  124. package/dist/product/twin/store.d.ts +21 -0
  125. package/dist/product/twin/store.js +70 -0
  126. package/dist/product/whatif/engine.d.ts +28 -0
  127. package/dist/product/whatif/engine.js +70 -0
  128. package/dist/utils/fs.js +10 -2
  129. package/package.json +1 -1
@@ -0,0 +1,215 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { DEFAULT_IGNORE_DIRECTORIES } from "../../constants.js";
5
+ import { isDirectory, pathExists } from "../../utils/fs.js";
6
+ import { resolveRepoRoot } from "../../utils/path.js";
7
+ /** Markers that strongly suggest a software project root. */
8
+ const PROJECT_MARKERS = [
9
+ "package.json",
10
+ "composer.json",
11
+ "pyproject.toml",
12
+ "Cargo.toml",
13
+ "go.mod",
14
+ "pom.xml",
15
+ "build.gradle",
16
+ "build.gradle.kts",
17
+ "Gemfile",
18
+ "mix.exs",
19
+ "pubspec.yaml",
20
+ ".git",
21
+ ];
22
+ const SUSPICIOUS_DIR_NAMES = new Set([
23
+ "desktop",
24
+ "downloads",
25
+ "documents",
26
+ "pictures",
27
+ "movies",
28
+ "music",
29
+ "library",
30
+ "applications",
31
+ ]);
32
+ function normalizeHome(home) {
33
+ return path.resolve(home);
34
+ }
35
+ function isUnderHomeSubtree(abs, home, leaf) {
36
+ const target = path.resolve(home, leaf);
37
+ const resolved = path.resolve(abs);
38
+ return resolved === target || resolved.startsWith(target + path.sep);
39
+ }
40
+ async function countEntriesShallow(dir, budget) {
41
+ let count = 0;
42
+ const queue = [dir];
43
+ const skip = DEFAULT_IGNORE_DIRECTORIES;
44
+ while (queue.length > 0 && count < budget) {
45
+ const current = queue.pop();
46
+ let entries;
47
+ try {
48
+ entries = await fs.readdir(current, { withFileTypes: true });
49
+ }
50
+ catch {
51
+ continue;
52
+ }
53
+ for (const entry of entries) {
54
+ count += 1;
55
+ if (count >= budget)
56
+ return count;
57
+ if (!entry.isDirectory() || entry.isSymbolicLink())
58
+ continue;
59
+ if (skip.has(entry.name) || entry.name.startsWith("."))
60
+ continue;
61
+ queue.push(path.join(current, entry.name));
62
+ }
63
+ }
64
+ return count;
65
+ }
66
+ async function markersAt(dir) {
67
+ const found = [];
68
+ for (const marker of PROJECT_MARKERS) {
69
+ if (await pathExists(path.join(dir, marker))) {
70
+ found.push(marker);
71
+ }
72
+ }
73
+ return found;
74
+ }
75
+ function scoreMarkers(markers) {
76
+ let score = 0;
77
+ for (const m of markers) {
78
+ if (m === ".git")
79
+ score += 3;
80
+ else if (m === "package.json" || m === "composer.json" || m === "pyproject.toml")
81
+ score += 5;
82
+ else
83
+ score += 4;
84
+ }
85
+ return score;
86
+ }
87
+ /**
88
+ * Discover likely project roots from cwd without silently scanning home/Desktop/Downloads
89
+ * or unbounded parent trees.
90
+ */
91
+ export async function discoverProjectRoots(options = {}) {
92
+ const cwd = path.resolve(options.cwd ?? process.cwd());
93
+ const home = normalizeHome(os.homedir());
94
+ const maxEntries = options.maxEntries ?? 25_000;
95
+ const limitations = [
96
+ "Discovery uses shallow marker probes and bounded entry counts — not a full repository index.",
97
+ "Candidates are VERIFIED only when marker files/directories exist on disk.",
98
+ ];
99
+ const baselower = path.basename(cwd).toLowerCase();
100
+ if (cwd === home ||
101
+ SUSPICIOUS_DIR_NAMES.has(baselower) ||
102
+ isUnderHomeSubtree(cwd, home, "Desktop") ||
103
+ isUnderHomeSubtree(cwd, home, "Downloads") ||
104
+ isUnderHomeSubtree(cwd, home, "Documents")) {
105
+ const estimatedEntries = await countEntriesShallow(cwd, Math.min(maxEntries, 5_000));
106
+ return {
107
+ cwd,
108
+ home,
109
+ candidates: [],
110
+ selected: null,
111
+ blocked: true,
112
+ blockReason: "Refusing to scan home / Desktop / Downloads / Documents (or similarly broad user folders) as a project root. Navigate into a project directory or pass an explicit project path.",
113
+ estimatedEntries,
114
+ limitations,
115
+ };
116
+ }
117
+ const estimatedEntries = await countEntriesShallow(cwd, maxEntries + 1);
118
+ if (estimatedEntries > maxEntries) {
119
+ return {
120
+ cwd,
121
+ home,
122
+ candidates: [],
123
+ selected: null,
124
+ blocked: true,
125
+ blockReason: `Directory tree exceeds safe scan budget (${maxEntries} entries). Narrow to a project subdirectory or raise --max-entries deliberately.`,
126
+ estimatedEntries,
127
+ limitations,
128
+ };
129
+ }
130
+ const candidates = [];
131
+ const seen = new Set();
132
+ async function consider(dir, reason) {
133
+ const resolved = path.resolve(dir);
134
+ if (seen.has(resolved))
135
+ return;
136
+ if (!(await isDirectory(resolved)))
137
+ return;
138
+ if (resolved === home)
139
+ return;
140
+ const markers = await markersAt(resolved);
141
+ if (markers.length === 0)
142
+ return;
143
+ seen.add(resolved);
144
+ candidates.push({
145
+ root: resolved,
146
+ score: scoreMarkers(markers),
147
+ markers,
148
+ truth: "VERIFIED",
149
+ reason,
150
+ });
151
+ }
152
+ if (options.prefer) {
153
+ await consider(options.prefer, "explicit prefer path");
154
+ }
155
+ await consider(cwd, "current working directory");
156
+ // Walk up a few parents looking for markers (bounded).
157
+ let parent = path.dirname(cwd);
158
+ for (let i = 0; i < 4; i++) {
159
+ if (parent === home || parent === path.dirname(parent))
160
+ break;
161
+ await consider(parent, "parent directory with project markers");
162
+ parent = path.dirname(parent);
163
+ }
164
+ // Immediate children that look like projects (monorepo packages / sibling apps).
165
+ try {
166
+ const entries = await fs.readdir(cwd, { withFileTypes: true });
167
+ for (const entry of entries) {
168
+ if (!entry.isDirectory() || entry.isSymbolicLink())
169
+ continue;
170
+ if (DEFAULT_IGNORE_DIRECTORIES.has(entry.name) || entry.name.startsWith("."))
171
+ continue;
172
+ await consider(path.join(cwd, entry.name), "child directory with project markers");
173
+ }
174
+ }
175
+ catch {
176
+ limitations.push("Could not list child directories for candidate discovery.");
177
+ }
178
+ candidates.sort((a, b) => b.score - a.score || a.root.localeCompare(b.root));
179
+ let selected = null;
180
+ if (options.prefer) {
181
+ selected = candidates.find((c) => c.root === path.resolve(options.prefer)) ?? null;
182
+ }
183
+ else if (options.autoSelect !== false) {
184
+ const strong = candidates.filter((c) => c.score >= 5);
185
+ if (strong.length === 1) {
186
+ selected = strong[0];
187
+ }
188
+ else if (candidates.length === 1) {
189
+ selected = candidates[0];
190
+ }
191
+ }
192
+ return {
193
+ cwd,
194
+ home,
195
+ candidates,
196
+ selected,
197
+ blocked: false,
198
+ blockReason: null,
199
+ estimatedEntries,
200
+ limitations,
201
+ };
202
+ }
203
+ export async function resolveStartedProjectRoot(pathArg, options) {
204
+ const prefer = pathArg ? resolveRepoRoot(pathArg) : undefined;
205
+ const discovery = await discoverProjectRoots({
206
+ cwd: prefer ?? process.cwd(),
207
+ ...(prefer !== undefined ? { prefer } : {}),
208
+ ...(options?.maxEntries !== undefined ? { maxEntries: options.maxEntries } : {}),
209
+ autoSelect: true,
210
+ });
211
+ if (discovery.blocked || !discovery.selected) {
212
+ return { ok: false, discovery };
213
+ }
214
+ return { ok: true, root: discovery.selected.root, discovery };
215
+ }
@@ -0,0 +1,50 @@
1
+ import type { TruthLabel } from "../truth.js";
2
+ export interface ProjectDna {
3
+ fingerprint: string;
4
+ name: string;
5
+ root: string;
6
+ projectType: string;
7
+ languages: string[];
8
+ frameworks: string[];
9
+ frontend: string[];
10
+ backend: string[];
11
+ database: string[];
12
+ packageManagers: string[];
13
+ buildSystems: string[];
14
+ testFrameworks: string[];
15
+ infrastructure: string[];
16
+ cloudIndicators: string[];
17
+ git: {
18
+ present: boolean;
19
+ truth: TruthLabel;
20
+ };
21
+ monorepo: {
22
+ isMonorepo: boolean;
23
+ tool: string;
24
+ packages: Array<{
25
+ name: string;
26
+ path: string;
27
+ }>;
28
+ truth: TruthLabel;
29
+ };
30
+ services: string[];
31
+ packages: string[];
32
+ applications: string[];
33
+ externalIntegrations: string[];
34
+ aiCodingTools: string[];
35
+ counts: {
36
+ files: number;
37
+ sourceFiles: number;
38
+ testFiles: number;
39
+ configFiles: number;
40
+ };
41
+ configurationStructure: string[];
42
+ truth: TruthLabel;
43
+ limitations: string[];
44
+ generatedAt: string;
45
+ }
46
+ /**
47
+ * Deterministic Project DNA from repository detectors + file inventory.
48
+ */
49
+ export declare function buildProjectDna(rootInput: string): Promise<ProjectDna>;
50
+ export declare function persistProjectDna(rootInput: string, dna?: ProjectDna): Promise<ProjectDna>;
@@ -0,0 +1,255 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { detectProject } from "../../detectors/project.js";
5
+ import { detectMonorepo } from "../../core/monorepo/detect.js";
6
+ import { resolveRepoRoot } from "../../utils/path.js";
7
+ import { pathExists, readJsonFile } from "../../utils/fs.js";
8
+ const FRONTEND_FW = new Set(["react", "nextjs", "vue", "nuxt", "angular", "svelte", "solid"]);
9
+ const BACKEND_FW = new Set([
10
+ "express",
11
+ "nestjs",
12
+ "fastapi",
13
+ "django",
14
+ "flask",
15
+ "laravel",
16
+ "rails",
17
+ "spring",
18
+ ]);
19
+ function classifyFramework(id) {
20
+ const lower = id.toLowerCase();
21
+ if (FRONTEND_FW.has(lower))
22
+ return "frontend";
23
+ if (BACKEND_FW.has(lower))
24
+ return "backend";
25
+ return "other";
26
+ }
27
+ function isSourcePath(p) {
28
+ return /\.(ts|tsx|js|jsx|mjs|cjs|py|php|go|rs|java|kt|kts|dart|rb|cs)$/i.test(p);
29
+ }
30
+ function isTestPath(p) {
31
+ const lower = p.toLowerCase();
32
+ return (lower.includes(".test.") ||
33
+ lower.includes(".spec.") ||
34
+ lower.startsWith("tests/") ||
35
+ lower.startsWith("test/") ||
36
+ lower.includes("/__tests__/"));
37
+ }
38
+ function isConfigPath(p) {
39
+ const base = path.posix.basename(p).toLowerCase();
40
+ return (base.endsWith(".json") ||
41
+ base.endsWith(".yml") ||
42
+ base.endsWith(".yaml") ||
43
+ base.endsWith(".toml") ||
44
+ base.endsWith(".ini") ||
45
+ base.startsWith(".env") ||
46
+ base.includes("config"));
47
+ }
48
+ /**
49
+ * Deterministic Project DNA from repository detectors + file inventory.
50
+ */
51
+ export async function buildProjectDna(rootInput) {
52
+ const root = resolveRepoRoot(rootInput);
53
+ const limitations = [
54
+ "DNA is derived from file markers and dependency manifests — not runtime introspection.",
55
+ "Database/cloud signals are heuristic (PARTIAL) unless schema/infra files are present.",
56
+ ];
57
+ const detection = await detectProject(root);
58
+ const mono = await detectMonorepo(root);
59
+ const repo = detection.repository;
60
+ const relativePaths = detection.discovery.files.map((f) => f.relativePath.replace(/\\/g, "/"));
61
+ const languages = (repo.languages ?? []).filter((l) => l !== "unknown");
62
+ const frameworks = (repo.frameworks ?? []).filter((f) => f !== "unknown");
63
+ const packageManagers = (repo.packageManagers ?? []).filter((p) => p !== "unknown");
64
+ const frontend = [];
65
+ const backend = [];
66
+ for (const fw of frameworks) {
67
+ const kind = classifyFramework(fw);
68
+ if (kind === "frontend")
69
+ frontend.push(fw);
70
+ else if (kind === "backend")
71
+ backend.push(fw);
72
+ }
73
+ const database = [];
74
+ const dbMarkers = [
75
+ ["prisma", /(^|\/)prisma\/schema\.prisma$/i],
76
+ ["sql-migrations", /(^|\/)migrations\/.+\.sql$/i],
77
+ ["laravel-migrations", /database\/migrations\/.+\.php$/i],
78
+ ["sequelize", /sequelize/i],
79
+ ["typeorm", /typeorm/i],
80
+ ["mongoose", /mongoose/i],
81
+ ];
82
+ for (const [label, re] of dbMarkers) {
83
+ if (relativePaths.some((p) => re.test(p)))
84
+ database.push(label);
85
+ }
86
+ const infrastructure = [];
87
+ const infraFiles = [
88
+ ["docker", "Dockerfile"],
89
+ ["compose", "docker-compose.yml"],
90
+ ["compose", "docker-compose.yaml"],
91
+ ["kubernetes", "k8s"],
92
+ ["terraform", ".tf"],
93
+ ["github-actions", ".github/workflows"],
94
+ ];
95
+ for (const [label, marker] of infraFiles) {
96
+ if (relativePaths.some((p) => p === marker ||
97
+ p.endsWith(`/${marker}`) ||
98
+ p.startsWith(`${marker}/`) ||
99
+ p.endsWith(marker))) {
100
+ if (!infrastructure.includes(label))
101
+ infrastructure.push(label);
102
+ }
103
+ }
104
+ const cloudIndicators = [];
105
+ for (const p of relativePaths) {
106
+ const lower = p.toLowerCase();
107
+ if (lower.includes("serverless") || lower.includes("sam.template"))
108
+ cloudIndicators.push("serverless");
109
+ if (lower.includes("vercel.json"))
110
+ cloudIndicators.push("vercel");
111
+ if (lower.includes("netlify.toml"))
112
+ cloudIndicators.push("netlify");
113
+ if (lower.includes("fly.toml"))
114
+ cloudIndicators.push("fly");
115
+ }
116
+ const buildSystems = [];
117
+ for (const [label, file] of [
118
+ ["tsc", "tsconfig.json"],
119
+ ["vite", "vite.config.ts"],
120
+ ["webpack", "webpack.config.js"],
121
+ ["gradle", "build.gradle"],
122
+ ["maven", "pom.xml"],
123
+ ["cargo", "Cargo.toml"],
124
+ ]) {
125
+ if (relativePaths.some((p) => p === file || p.endsWith(`/${file}`)))
126
+ buildSystems.push(label);
127
+ }
128
+ const testFrameworks = [];
129
+ const pkg = await readJsonFile(path.join(root, "package.json"), 512 * 1024);
130
+ const deps = {
131
+ ...(pkg.ok ? (pkg.data.dependencies ?? {}) : {}),
132
+ ...(pkg.ok ? (pkg.data.devDependencies ?? {}) : {}),
133
+ };
134
+ for (const name of ["vitest", "jest", "mocha", "pytest", "phpunit", "go test"]) {
135
+ if (name === "go test") {
136
+ if (await pathExists(path.join(root, "go.mod")))
137
+ testFrameworks.push("go-test");
138
+ continue;
139
+ }
140
+ if (name in deps || relativePaths.some((p) => p.includes(name))) {
141
+ if (!testFrameworks.includes(name))
142
+ testFrameworks.push(name);
143
+ }
144
+ }
145
+ // Marker scan for common AI coding tool configuration directories/files
146
+ const aiCodingTools = [];
147
+ for (const [id, marker] of [
148
+ ["cursor", ".cursor"],
149
+ ["claude-code", ".claude"],
150
+ ["codex", ".codex"],
151
+ ["copilot", ".github/copilot-instructions.md"],
152
+ ["windsurf", ".windsurfrules"],
153
+ ["gemini-cli", ".gemini"],
154
+ ["aider", ".aider"],
155
+ ]) {
156
+ if (relativePaths.some((p) => p === marker || p.startsWith(`${marker}/`) || p.endsWith(`/${marker}`)) ||
157
+ (await pathExists(path.join(root, marker)))) {
158
+ aiCodingTools.push(id);
159
+ }
160
+ }
161
+ const hasGit = await pathExists(path.join(root, ".git"));
162
+ const name = (pkg.ok && typeof pkg.data.name === "string" && pkg.data.name) || path.basename(root);
163
+ let projectType = "library-or-app";
164
+ if (mono.isMonorepo)
165
+ projectType = "monorepo";
166
+ else if (frontend.length && backend.length)
167
+ projectType = "fullstack-app";
168
+ else if (frontend.length)
169
+ projectType = "frontend-app";
170
+ else if (backend.length)
171
+ projectType = "backend-app";
172
+ else if (languages.includes("python"))
173
+ projectType = "python-project";
174
+ else if (languages.includes("php"))
175
+ projectType = "php-project";
176
+ const sourceFiles = relativePaths.filter(isSourcePath).length;
177
+ const testFiles = relativePaths.filter(isTestPath).length;
178
+ const configFiles = relativePaths.filter(isConfigPath).length;
179
+ const configurationStructure = [
180
+ ...new Set(relativePaths
181
+ .filter(isConfigPath)
182
+ .map((p) => p.split("/").slice(0, 2).join("/"))
183
+ .slice(0, 40)),
184
+ ].sort();
185
+ const packages = mono.packages.map((p) => p.name);
186
+ const applications = mono.packages
187
+ .filter((p) => /app|web|api|service/i.test(p.relativePath) || /app|web|api/i.test(p.name))
188
+ .map((p) => p.name);
189
+ const services = applications.slice();
190
+ const externalIntegrations = [];
191
+ for (const key of Object.keys(deps)) {
192
+ if (/stripe|twilio|sendgrid|aws-sdk|@aws-sdk|firebase|supabase|openai|anthropic/i.test(key)) {
193
+ externalIntegrations.push(key);
194
+ }
195
+ }
196
+ const fingerprintPayload = JSON.stringify({
197
+ name,
198
+ languages: [...languages].sort(),
199
+ frameworks: [...frameworks].sort(),
200
+ packageManagers: [...packageManagers].sort(),
201
+ monorepo: mono.isMonorepo,
202
+ packages: [...packages].sort(),
203
+ fileCount: relativePaths.length,
204
+ });
205
+ const fingerprint = createHash("sha256").update(fingerprintPayload).digest("hex").slice(0, 16);
206
+ return {
207
+ fingerprint,
208
+ name,
209
+ root,
210
+ projectType,
211
+ languages,
212
+ frameworks,
213
+ frontend,
214
+ backend,
215
+ database: [...new Set(database)],
216
+ packageManagers,
217
+ buildSystems: [...new Set(buildSystems)],
218
+ testFrameworks: [...new Set(testFrameworks)],
219
+ infrastructure: [...new Set(infrastructure)],
220
+ cloudIndicators: [...new Set(cloudIndicators)],
221
+ git: { present: hasGit, truth: hasGit ? "VERIFIED" : "UNKNOWN" },
222
+ monorepo: {
223
+ isMonorepo: mono.isMonorepo,
224
+ tool: mono.tool,
225
+ packages: mono.packages.map((p) => ({ name: p.name, path: p.relativePath })),
226
+ truth: mono.isMonorepo ? "VERIFIED" : "VERIFIED",
227
+ },
228
+ services,
229
+ packages,
230
+ applications,
231
+ externalIntegrations: [...new Set(externalIntegrations)].sort(),
232
+ aiCodingTools: [...new Set(aiCodingTools)].sort(),
233
+ counts: {
234
+ files: relativePaths.length,
235
+ sourceFiles,
236
+ testFiles,
237
+ configFiles,
238
+ },
239
+ configurationStructure,
240
+ truth: "VERIFIED",
241
+ limitations: [...limitations, ...mono.limitations],
242
+ generatedAt: new Date().toISOString(),
243
+ };
244
+ }
245
+ export async function persistProjectDna(rootInput, dna) {
246
+ const root = resolveRepoRoot(rootInput);
247
+ const built = dna ?? (await buildProjectDna(root));
248
+ const dir = path.join(root, ".agentdoctor", "dna");
249
+ await fs.mkdir(dir, { recursive: true });
250
+ await fs.writeFile(path.join(dir, "project-dna.json"), `${JSON.stringify(built, null, 2)}\n`, {
251
+ encoding: "utf8",
252
+ mode: 0o600,
253
+ });
254
+ return built;
255
+ }
@@ -0,0 +1,17 @@
1
+ import type { TruthLabel } from "../truth.js";
2
+ export interface EvalCheckResult {
3
+ id: string;
4
+ description: string;
5
+ passed: boolean;
6
+ truth: TruthLabel;
7
+ detail?: string;
8
+ }
9
+ export interface EvalLabReport {
10
+ root: string;
11
+ fixtureRoot: string;
12
+ fixturesRun: string[];
13
+ checks: EvalCheckResult[];
14
+ passed: boolean;
15
+ limitations: string[];
16
+ }
17
+ export declare function runEvalLab(rootInput?: string): Promise<EvalLabReport>;