@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,234 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { PACKAGE_VERSION } from "../../constants.js";
6
+ import { resolveRepoRoot, toPosixRelative } from "../../utils/path.js";
7
+ import { atomicWriteTextFile } from "../../utils/fs.js";
8
+ import { buildIntelligenceGraph, listTsFiles } from "./build.js";
9
+ export const GRAPH_INDEX_SCHEMA = "1.0.0";
10
+ function indexPath(root) {
11
+ return path.join(root, ".agentdoctor", "graph", "index.json");
12
+ }
13
+ function sha256FileContent(content) {
14
+ return createHash("sha256").update(content).digest("hex");
15
+ }
16
+ async function hashFiles(root, absFiles) {
17
+ const out = {};
18
+ for (const abs of absFiles) {
19
+ const rel = toPosixRelative(root, abs);
20
+ try {
21
+ const buf = await fs.readFile(abs);
22
+ out[rel] = sha256FileContent(buf);
23
+ }
24
+ catch {
25
+ // skip unreadable
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ function gitChangedFiles(root) {
31
+ const run = (args) => {
32
+ const r = spawnSync("git", args, { cwd: root, encoding: "utf8" });
33
+ return r.status === 0 && typeof r.stdout === "string" ? r.stdout : null;
34
+ };
35
+ const staged = run(["diff", "--name-only", "--cached"]);
36
+ const unstaged = run(["diff", "--name-only"]);
37
+ const untracked = run(["ls-files", "--others", "--exclude-standard"]);
38
+ if (staged === null && unstaged === null && untracked === null)
39
+ return null;
40
+ const set = new Set();
41
+ for (const block of [staged, unstaged, untracked]) {
42
+ if (!block)
43
+ continue;
44
+ for (const line of block.split("\n")) {
45
+ const t = line.trim();
46
+ if (t)
47
+ set.add(t.split(path.sep).join("/"));
48
+ }
49
+ }
50
+ return [...set].sort();
51
+ }
52
+ function isValidSnapshot(raw) {
53
+ if (!raw || typeof raw !== "object")
54
+ return false;
55
+ const s = raw;
56
+ return (typeof s.schemaVersion === "string" &&
57
+ typeof s.root === "string" &&
58
+ typeof s.generatedAt === "string" &&
59
+ s.fileHashes !== null &&
60
+ typeof s.fileHashes === "object" &&
61
+ Array.isArray(s.nodes) &&
62
+ Array.isArray(s.edges));
63
+ }
64
+ async function loadSnapshot(root) {
65
+ const file = indexPath(root);
66
+ try {
67
+ const raw = await fs.readFile(file, "utf8");
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(raw);
71
+ }
72
+ catch {
73
+ return { ok: false, corrupt: true, error: "index.json is not valid JSON" };
74
+ }
75
+ if (!isValidSnapshot(parsed)) {
76
+ return { ok: false, corrupt: true, error: "index.json failed schema checks" };
77
+ }
78
+ return { ok: true, snapshot: parsed };
79
+ }
80
+ catch (error) {
81
+ return {
82
+ ok: false,
83
+ corrupt: false,
84
+ error: error instanceof Error ? error.message : "index missing",
85
+ };
86
+ }
87
+ }
88
+ async function persistSnapshot(root, snapshot) {
89
+ const file = indexPath(root);
90
+ await fs.mkdir(path.dirname(file), { recursive: true });
91
+ await atomicWriteTextFile(file, `${JSON.stringify(snapshot, null, 2)}\n`);
92
+ return file;
93
+ }
94
+ async function buildAndPersist(root, mode = "auto") {
95
+ const graph = await buildIntelligenceGraph({ root, mode });
96
+ const files = await listTsFiles(root);
97
+ const fileHashes = await hashFiles(root, files);
98
+ const snapshot = {
99
+ schemaVersion: GRAPH_INDEX_SCHEMA,
100
+ agentDoctorVersion: PACKAGE_VERSION,
101
+ root,
102
+ generatedAt: graph.generatedAt,
103
+ builder: graph.builder,
104
+ astFilesParsed: graph.astFilesParsed,
105
+ fileHashes,
106
+ nodes: graph.nodes,
107
+ edges: graph.edges,
108
+ limitations: [
109
+ ...graph.limitations,
110
+ "Incremental index persists file hashes for change detection",
111
+ ],
112
+ };
113
+ const saved = await persistSnapshot(root, snapshot);
114
+ return { snapshot, path: saved };
115
+ }
116
+ /**
117
+ * Full graph build and persist to `.agentdoctor/graph/index.json`.
118
+ */
119
+ export async function graphBuild(options) {
120
+ const root = resolveRepoRoot(options.root);
121
+ const result = await buildAndPersist(root, options.mode ?? "auto");
122
+ return { ...result, rebuilt: true };
123
+ }
124
+ /**
125
+ * Force full rebuild (same as build, clears prior assumptions).
126
+ */
127
+ export async function graphRebuild(options) {
128
+ return graphBuild(options);
129
+ }
130
+ /**
131
+ * Update graph from git-changed files or content-hash comparison.
132
+ * On corruption → rebuild. When nothing changed → reuse snapshot.
133
+ */
134
+ export async function graphUpdate(options) {
135
+ const root = resolveRepoRoot(options.root);
136
+ const loaded = await loadSnapshot(root);
137
+ if (!loaded.ok) {
138
+ if (loaded.corrupt) {
139
+ const result = await buildAndPersist(root, options.mode ?? "auto");
140
+ return {
141
+ ...result,
142
+ rebuilt: true,
143
+ reason: `corrupt index rebuilt: ${loaded.error}`,
144
+ changedFiles: Object.keys(result.snapshot.fileHashes),
145
+ };
146
+ }
147
+ const result = await buildAndPersist(root, options.mode ?? "auto");
148
+ return {
149
+ ...result,
150
+ rebuilt: true,
151
+ reason: "no prior index; built fresh",
152
+ changedFiles: Object.keys(result.snapshot.fileHashes),
153
+ };
154
+ }
155
+ const files = await listTsFiles(root);
156
+ const currentHashes = await hashFiles(root, files);
157
+ const prev = loaded.snapshot.fileHashes;
158
+ const changedFromHash = [];
159
+ for (const [rel, hash] of Object.entries(currentHashes)) {
160
+ if (prev[rel] !== hash)
161
+ changedFromHash.push(rel);
162
+ }
163
+ for (const rel of Object.keys(prev)) {
164
+ if (!(rel in currentHashes))
165
+ changedFromHash.push(rel);
166
+ }
167
+ const gitFiles = gitChangedFiles(root);
168
+ const tsGitChanged = gitFiles?.filter((f) => /\.(ts|tsx|mts|cts)$/i.test(f) && !f.endsWith(".d.ts")) ?? [];
169
+ const changedSet = new Set([...changedFromHash, ...tsGitChanged]);
170
+ const changedFiles = [...changedSet].sort();
171
+ if (changedFiles.length === 0) {
172
+ return {
173
+ snapshot: loaded.snapshot,
174
+ path: indexPath(root),
175
+ rebuilt: false,
176
+ reason: "file hashes unchanged; snapshot reused",
177
+ changedFiles: [],
178
+ };
179
+ }
180
+ // Incremental: drop nodes/edges for changed paths, re-run full builder for correctness
181
+ // (TypeScript program needs whole set; hash gate already skipped no-op updates).
182
+ const result = await buildAndPersist(root, options.mode ?? "auto");
183
+ return {
184
+ ...result,
185
+ rebuilt: true,
186
+ reason: `updated ${changedFiles.length} changed file(s)`,
187
+ changedFiles,
188
+ };
189
+ }
190
+ export async function graphStatus(options) {
191
+ const root = resolveRepoRoot(options.root);
192
+ const file = indexPath(root);
193
+ const loaded = await loadSnapshot(root);
194
+ if (!loaded.ok) {
195
+ return {
196
+ present: false,
197
+ path: file,
198
+ nodeCount: 0,
199
+ edgeCount: 0,
200
+ fileCount: 0,
201
+ corrupt: loaded.corrupt,
202
+ error: loaded.error,
203
+ staleFiles: [],
204
+ missingFiles: [],
205
+ };
206
+ }
207
+ const files = await listTsFiles(root);
208
+ const currentHashes = await hashFiles(root, files);
209
+ const prev = loaded.snapshot.fileHashes;
210
+ const staleFiles = [];
211
+ const missingFiles = [];
212
+ for (const [rel, hash] of Object.entries(currentHashes)) {
213
+ if (!(rel in prev))
214
+ staleFiles.push(rel);
215
+ else if (prev[rel] !== hash)
216
+ staleFiles.push(rel);
217
+ }
218
+ for (const rel of Object.keys(prev)) {
219
+ if (!(rel in currentHashes))
220
+ missingFiles.push(rel);
221
+ }
222
+ return {
223
+ present: true,
224
+ path: file,
225
+ schemaVersion: loaded.snapshot.schemaVersion,
226
+ builder: String(loaded.snapshot.builder),
227
+ nodeCount: loaded.snapshot.nodes.length,
228
+ edgeCount: loaded.snapshot.edges.length,
229
+ fileCount: Object.keys(prev).length,
230
+ generatedAt: loaded.snapshot.generatedAt,
231
+ staleFiles: staleFiles.sort(),
232
+ missingFiles: missingFiles.sort(),
233
+ };
234
+ }
@@ -0,0 +1,36 @@
1
+ export type ImportConfidence = "EXACT" | "RESOLVED" | "INFERRED" | "UNRESOLVED";
2
+ export interface TsconfigPathsConfig {
3
+ baseUrl: string;
4
+ paths: Record<string, string[]>;
5
+ configPath: string;
6
+ }
7
+ export interface ImportResolutionResult {
8
+ specifier: string;
9
+ fromFile: string;
10
+ resolvedPath: string | null;
11
+ confidence: ImportConfidence;
12
+ via?: string;
13
+ }
14
+ /**
15
+ * Read tsconfig.json paths/baseUrl when present. Returns null when absent or unreadable.
16
+ * Does not invent mappings.
17
+ */
18
+ export declare function loadTsconfigPaths(root: string): TsconfigPathsConfig | null;
19
+ /**
20
+ * Resolve an import specifier relative to `fromFile` (repo-relative POSIX).
21
+ * Never invents a path for UNRESOLVED — `resolvedPath` is null.
22
+ */
23
+ export declare function resolveImportSpecifier(options: {
24
+ root: string;
25
+ fromFile: string;
26
+ specifier: string;
27
+ tsconfig?: TsconfigPathsConfig | null;
28
+ fileExists?: (absolutePath: string) => boolean;
29
+ }): ImportResolutionResult;
30
+ /**
31
+ * Resolve many import edges; filters to those with a concrete resolved path.
32
+ */
33
+ export declare function resolveImportEdges(root: string, imports: Array<{
34
+ fromFile: string;
35
+ specifier: string;
36
+ }>, tsconfig?: TsconfigPathsConfig | null): ImportResolutionResult[];
@@ -0,0 +1,245 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const FILE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
4
+ function toPosix(p) {
5
+ return p.split(path.sep).join("/");
6
+ }
7
+ function fileExists(abs, existsFn) {
8
+ if (existsFn)
9
+ return existsFn(abs);
10
+ try {
11
+ return fs.statSync(abs).isFile();
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ function dirExists(abs, existsFn) {
18
+ if (existsFn) {
19
+ // Approximate: any index file under the dir.
20
+ return FILE_EXTENSIONS.some((ext) => existsFn(path.join(abs, `index${ext}`)));
21
+ }
22
+ try {
23
+ return fs.statSync(abs).isDirectory();
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Try exact file, extension variants, then index/barrel.
31
+ * Returns confidence EXACT when the written path exists as-is,
32
+ * RESOLVED when found via extension or index.
33
+ */
34
+ function resolveFilesystemCandidate(root, absWithoutExt, existsFn) {
35
+ const tryFile = (abs, confidence, via) => {
36
+ if (fileExists(abs, existsFn)) {
37
+ return { abs, confidence, via };
38
+ }
39
+ return null;
40
+ };
41
+ const exact = tryFile(absWithoutExt, "EXACT", "exact");
42
+ if (exact)
43
+ return exact;
44
+ for (const ext of FILE_EXTENSIONS) {
45
+ if (absWithoutExt.endsWith(ext))
46
+ continue;
47
+ const hit = tryFile(`${absWithoutExt}${ext}`, "RESOLVED", `extension:${ext}`);
48
+ if (hit)
49
+ return hit;
50
+ }
51
+ if (dirExists(absWithoutExt, existsFn)) {
52
+ for (const ext of FILE_EXTENSIONS) {
53
+ const hit = tryFile(path.join(absWithoutExt, `index${ext}`), "RESOLVED", `index:index${ext}`);
54
+ if (hit)
55
+ return hit;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ function stripJsonComments(text) {
61
+ // Minimal strip for // and /* */ outside strings — enough for typical tsconfig.
62
+ let out = "";
63
+ let i = 0;
64
+ let inString = null;
65
+ while (i < text.length) {
66
+ const ch = text[i];
67
+ if (inString) {
68
+ out += ch;
69
+ if (ch === "\\" && i + 1 < text.length) {
70
+ out += text[i + 1];
71
+ i += 2;
72
+ continue;
73
+ }
74
+ if (ch === inString)
75
+ inString = null;
76
+ i += 1;
77
+ continue;
78
+ }
79
+ if (ch === '"' || ch === "'") {
80
+ inString = ch;
81
+ out += ch;
82
+ i += 1;
83
+ continue;
84
+ }
85
+ if (ch === "/" && text[i + 1] === "/") {
86
+ i += 2;
87
+ while (i < text.length && text[i] !== "\n")
88
+ i += 1;
89
+ continue;
90
+ }
91
+ if (ch === "/" && text[i + 1] === "*") {
92
+ i += 2;
93
+ while (i + 1 < text.length && !(text[i] === "*" && text[i + 1] === "/"))
94
+ i += 1;
95
+ i += 2;
96
+ continue;
97
+ }
98
+ // Trailing commas before } or ]
99
+ if (ch === "," && (text.slice(i + 1).match(/^\s*[}\]]/) ?? false)) {
100
+ i += 1;
101
+ continue;
102
+ }
103
+ out += ch;
104
+ i += 1;
105
+ }
106
+ return out;
107
+ }
108
+ /**
109
+ * Read tsconfig.json paths/baseUrl when present. Returns null when absent or unreadable.
110
+ * Does not invent mappings.
111
+ */
112
+ export function loadTsconfigPaths(root) {
113
+ const configPath = path.join(root, "tsconfig.json");
114
+ let raw;
115
+ try {
116
+ raw = fs.readFileSync(configPath, "utf8");
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ try {
122
+ const parsed = JSON.parse(stripJsonComments(raw));
123
+ const baseUrl = parsed.compilerOptions?.baseUrl ?? ".";
124
+ const paths = parsed.compilerOptions?.paths ?? {};
125
+ return { baseUrl, paths, configPath };
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
131
+ function matchTsconfigPath(specifier, config) {
132
+ const entries = Object.entries(config.paths);
133
+ // Prefer longest pattern key for specificity.
134
+ entries.sort((a, b) => b[0].length - a[0].length);
135
+ for (const [pattern, targets] of entries) {
136
+ if (!targets.length)
137
+ continue;
138
+ if (pattern.includes("*")) {
139
+ const [prefix, suffix] = pattern.split("*");
140
+ if (typeof prefix === "string" &&
141
+ typeof suffix === "string" &&
142
+ specifier.startsWith(prefix) &&
143
+ specifier.endsWith(suffix)) {
144
+ const star = specifier.slice(prefix.length, specifier.length - suffix.length);
145
+ const target = targets[0];
146
+ const mapped = target.includes("*") ? target.replace("*", star) : target;
147
+ return { mapped, pattern };
148
+ }
149
+ }
150
+ else if (specifier === pattern) {
151
+ return { mapped: targets[0], pattern };
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ /**
157
+ * Resolve an import specifier relative to `fromFile` (repo-relative POSIX).
158
+ * Never invents a path for UNRESOLVED — `resolvedPath` is null.
159
+ */
160
+ export function resolveImportSpecifier(options) {
161
+ const { root, fromFile, specifier } = options;
162
+ const fromPosix = toPosix(fromFile);
163
+ const base = {
164
+ specifier,
165
+ fromFile: fromPosix,
166
+ };
167
+ if (!specifier || specifier.includes("\0")) {
168
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
169
+ }
170
+ const existsFn = options.fileExists;
171
+ const toRel = (abs) => toPosix(path.relative(root, abs));
172
+ // Relative imports
173
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
174
+ const fromDir = path.dirname(path.join(root, fromPosix));
175
+ const abs = path.resolve(fromDir, specifier);
176
+ // Containment: refuse escaping the repo when resolving relatives.
177
+ const rel = path.relative(root, abs);
178
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
179
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
180
+ }
181
+ const hit = resolveFilesystemCandidate(root, abs, existsFn);
182
+ if (!hit) {
183
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
184
+ }
185
+ return {
186
+ ...base,
187
+ resolvedPath: toRel(hit.abs),
188
+ confidence: hit.confidence,
189
+ via: hit.via,
190
+ };
191
+ }
192
+ // tsconfig paths / baseUrl (non-relative)
193
+ const tsconfig = options.tsconfig === undefined ? loadTsconfigPaths(root) : options.tsconfig;
194
+ if (tsconfig) {
195
+ const matched = matchTsconfigPath(specifier, tsconfig);
196
+ if (matched) {
197
+ const abs = path.resolve(root, tsconfig.baseUrl, matched.mapped);
198
+ const rel = path.relative(root, abs);
199
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
200
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
201
+ }
202
+ const hit = resolveFilesystemCandidate(root, abs, existsFn);
203
+ if (hit) {
204
+ return {
205
+ ...base,
206
+ resolvedPath: toRel(hit.abs),
207
+ confidence: "RESOLVED",
208
+ via: `tsconfig-paths:${matched.pattern}`,
209
+ };
210
+ }
211
+ // Pattern matched but file missing — do not invent.
212
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED", via: "tsconfig-paths-miss" };
213
+ }
214
+ // baseUrl bare specifier (e.g. "utils/foo" with baseUrl=src) — INFERRED only when found.
215
+ if (!specifier.startsWith(".") && !specifier.includes(":")) {
216
+ const abs = path.resolve(root, tsconfig.baseUrl, specifier);
217
+ const rel = path.relative(root, abs);
218
+ if (!rel.startsWith("..") && !path.isAbsolute(rel)) {
219
+ const hit = resolveFilesystemCandidate(root, abs, existsFn);
220
+ if (hit) {
221
+ return {
222
+ ...base,
223
+ resolvedPath: toRel(hit.abs),
224
+ confidence: "INFERRED",
225
+ via: "tsconfig-baseUrl",
226
+ };
227
+ }
228
+ }
229
+ }
230
+ }
231
+ // Package / URL / unknown — unresolved; never invent a repo edge.
232
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
233
+ }
234
+ /**
235
+ * Resolve many import edges; filters to those with a concrete resolved path.
236
+ */
237
+ export function resolveImportEdges(root, imports, tsconfig) {
238
+ const cfg = tsconfig === undefined ? loadTsconfigPaths(root) : tsconfig;
239
+ return imports.map((item) => resolveImportSpecifier({
240
+ root,
241
+ fromFile: item.fromFile,
242
+ specifier: item.specifier,
243
+ tsconfig: cfg,
244
+ }));
245
+ }
@@ -0,0 +1,25 @@
1
+ import type { KnowledgeRecordContract, KnowledgeStatus } from "../contracts/index.js";
2
+ import type { StorageProvider } from "../contracts/index.js";
3
+ export declare function createKnowledgeRecord(options: {
4
+ root: string;
5
+ title: string;
6
+ content: string;
7
+ status?: KnowledgeStatus;
8
+ owner?: string;
9
+ storage?: StorageProvider;
10
+ }): Promise<KnowledgeRecordContract>;
11
+ export declare function transitionKnowledge(options: {
12
+ root: string;
13
+ id: string;
14
+ to: KnowledgeStatus;
15
+ by?: string;
16
+ note?: string;
17
+ storage?: StorageProvider;
18
+ }): Promise<KnowledgeRecordContract>;
19
+ export declare function listKnowledge(root: string, storage?: StorageProvider): Promise<KnowledgeRecordContract[]>;
20
+ /** Authoritative retrieval abstains when no approved record matches. */
21
+ export declare function retrieveAuthoritative(records: KnowledgeRecordContract[], query: string): {
22
+ record: KnowledgeRecordContract | null;
23
+ abstain: boolean;
24
+ reason: string;
25
+ };
@@ -0,0 +1,91 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { FilesystemStorageProvider } from "../storage/provider.js";
3
+ import { resolveRepoRoot } from "../utils/path.js";
4
+ function kid(title) {
5
+ return `know_${createHash("sha1").update(title).digest("hex").slice(0, 12)}`;
6
+ }
7
+ export async function createKnowledgeRecord(options) {
8
+ const storage = options.storage ?? new FilesystemStorageProvider(options.root, ".agentdoctor/knowledge");
9
+ const record = {
10
+ id: kid(`${options.title}:${randomUUID()}`),
11
+ title: options.title,
12
+ content: options.content,
13
+ status: options.status ?? "draft",
14
+ owner: options.owner ?? null,
15
+ approver: null,
16
+ version: "1",
17
+ evidence: [
18
+ {
19
+ id: "ev_create",
20
+ kind: "proposed",
21
+ detail: "Created via knowledge governance API",
22
+ },
23
+ ],
24
+ relatedRepositoryId: resolveRepoRoot(options.root),
25
+ changeHistory: [
26
+ {
27
+ at: new Date().toISOString(),
28
+ note: "created",
29
+ to: options.status ?? "draft",
30
+ ...(options.owner ? { by: options.owner } : {}),
31
+ },
32
+ ],
33
+ };
34
+ await storage.set(`records/${record.id}.json`, `${JSON.stringify(record, null, 2)}\n`);
35
+ return record;
36
+ }
37
+ export async function transitionKnowledge(options) {
38
+ const storage = options.storage ?? new FilesystemStorageProvider(options.root, ".agentdoctor/knowledge");
39
+ const raw = await storage.get(`records/${options.id}.json`);
40
+ if (!raw)
41
+ throw new Error(`knowledge record not found: ${options.id}`);
42
+ const record = JSON.parse(raw);
43
+ const from = record.status;
44
+ // Authority: only pending-review/draft/proposed can become approved via explicit transition
45
+ if (options.to === "approved" && !["draft", "proposed", "pending-review"].includes(from)) {
46
+ throw new Error(`cannot approve from status ${from}`);
47
+ }
48
+ record.status = options.to;
49
+ if (options.to === "approved") {
50
+ record.approver = options.by ?? "local-approver";
51
+ record.effectiveAt = new Date().toISOString();
52
+ }
53
+ record.changeHistory.push({
54
+ at: new Date().toISOString(),
55
+ note: options.note ?? `transition ${from} -> ${options.to}`,
56
+ from,
57
+ to: options.to,
58
+ ...(options.by ? { by: options.by } : {}),
59
+ });
60
+ record.version = String(Number(record.version || "1") + 1);
61
+ await storage.set(`records/${record.id}.json`, `${JSON.stringify(record, null, 2)}\n`);
62
+ return record;
63
+ }
64
+ export async function listKnowledge(root, storage) {
65
+ const store = storage ?? new FilesystemStorageProvider(root, ".agentdoctor/knowledge");
66
+ const keys = await store.list("records");
67
+ const out = [];
68
+ for (const key of keys) {
69
+ if (!key.endsWith(".json"))
70
+ continue;
71
+ const raw = await store.get(key.startsWith("records/") ? key : `records/${key}`);
72
+ if (!raw)
73
+ continue;
74
+ out.push(JSON.parse(raw));
75
+ }
76
+ return out.sort((a, b) => a.id.localeCompare(b.id));
77
+ }
78
+ /** Authoritative retrieval abstains when no approved record matches. */
79
+ export function retrieveAuthoritative(records, query) {
80
+ const q = query.toLowerCase();
81
+ const approved = records.filter((r) => r.status === "approved" &&
82
+ (r.title.toLowerCase().includes(q) || r.content.toLowerCase().includes(q)));
83
+ if (approved.length === 0) {
84
+ return {
85
+ record: null,
86
+ abstain: true,
87
+ reason: "No approved authoritative knowledge matched the query",
88
+ };
89
+ }
90
+ return { record: approved[0], abstain: false, reason: "approved match" };
91
+ }
@@ -0,0 +1,9 @@
1
+ import type { LanguageAdapter } from "./types.js";
2
+ /**
3
+ * Go toolchain bridge placeholder.
4
+ * Prefer real `go/ast` via `go run` when the Go toolchain is present; until an
5
+ * extractor is bundled, capability remains unsupported even if `go` exists.
6
+ * Today: always unsupported (no bundled extractor; avoids fake AST claims).
7
+ */
8
+ export declare function goAvailable(): boolean;
9
+ export declare const goAdapter: LanguageAdapter;
@@ -0,0 +1,45 @@
1
+ import { spawnSync } from "node:child_process";
2
+ /**
3
+ * Go toolchain bridge placeholder.
4
+ * Prefer real `go/ast` via `go run` when the Go toolchain is present; until an
5
+ * extractor is bundled, capability remains unsupported even if `go` exists.
6
+ * Today: always unsupported (no bundled extractor; avoids fake AST claims).
7
+ */
8
+ export function goAvailable() {
9
+ const r = spawnSync("go", ["version"], { encoding: "utf8" });
10
+ return r.status === 0;
11
+ }
12
+ export const goAdapter = {
13
+ id: "go",
14
+ extensions: [".go"],
15
+ capabilities: () => ({
16
+ parse: "unsupported",
17
+ symbols: "unsupported",
18
+ definitions: "unsupported",
19
+ references: "unsupported",
20
+ imports: "unsupported",
21
+ calls: "unsupported",
22
+ diagnostics: "unsupported",
23
+ }),
24
+ async parse(filePath, _source) {
25
+ const hasGo = goAvailable();
26
+ return {
27
+ language: "go",
28
+ file: filePath,
29
+ ok: false,
30
+ capabilities: goAdapter.capabilities(),
31
+ symbols: [],
32
+ imports: [],
33
+ calls: [],
34
+ diagnostics: [
35
+ hasGo
36
+ ? "Go toolchain present but AgentDoctor go/ast extractor is not bundled yet"
37
+ : "go not available on PATH",
38
+ ],
39
+ limitations: [
40
+ "Go AST requires a go/ast bridge or tree-sitter-go; not shipped in this package",
41
+ "Capability remains unsupported — no regex fallback under an AST claim",
42
+ ],
43
+ };
44
+ },
45
+ };