@praneeth_54/agentdoctor 1.1.1 → 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 +95 -7
  2. package/README.md +163 -538
  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 +8 -4
@@ -0,0 +1,45 @@
1
+ import type { KnowledgeStatus } from "../../contracts/index.js";
2
+ export interface ProjectInitAnswers {
3
+ projectName: string;
4
+ projectType: string;
5
+ businessDomain: string;
6
+ languages: string[];
7
+ frameworks: string[];
8
+ database: string;
9
+ deploymentModel: string;
10
+ teamStructure: string;
11
+ securityRequirements: string;
12
+ preferredArchitecture: string;
13
+ aiAgentEnvironments: string[];
14
+ }
15
+ export interface BrainProposalArtifact {
16
+ id: string;
17
+ kind: string;
18
+ title: string;
19
+ path: string;
20
+ status: KnowledgeStatus;
21
+ content: string;
22
+ }
23
+ export interface BrainReviewItem {
24
+ id: string;
25
+ artifactId: string;
26
+ status: KnowledgeStatus;
27
+ decidedBy?: string;
28
+ decidedAt?: string;
29
+ note?: string;
30
+ }
31
+ export declare function ensureRepositoryBrainDirs(rootInput: string): Promise<string>;
32
+ /** Generate draft proposal artifacts — never auto-approved. */
33
+ export declare function runProjectInit(rootInput: string, answers: Partial<ProjectInitAnswers>): Promise<{
34
+ root: string;
35
+ artifacts: BrainProposalArtifact[];
36
+ }>;
37
+ export declare function listProposals(rootInput: string): Promise<BrainProposalArtifact[]>;
38
+ export declare function reviewProposal(options: {
39
+ root: string;
40
+ artifactId: string;
41
+ decision: "approved" | "rejected" | "pending-review" | "deprecated";
42
+ by?: string;
43
+ note?: string;
44
+ }): Promise<BrainReviewItem>;
45
+ export declare function writeBrainProductSnapshot(rootInput: string): Promise<string>;
@@ -0,0 +1,232 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { resolveRepoRoot, isPathInsideRoot } from "../../utils/path.js";
5
+ import { atomicWriteTextFile } from "../../utils/fs.js";
6
+ function proposalsDir(root) {
7
+ return path.join(resolveRepoRoot(root), ".agentdoctor", "repository-brain", "proposals");
8
+ }
9
+ function reviewFile(root) {
10
+ return path.join(resolveRepoRoot(root), ".agentdoctor", "repository-brain", "reviews.json");
11
+ }
12
+ export async function ensureRepositoryBrainDirs(rootInput) {
13
+ const root = resolveRepoRoot(rootInput);
14
+ const base = path.join(root, ".agentdoctor", "repository-brain");
15
+ await fs.mkdir(path.join(base, "proposals"), { recursive: true });
16
+ await fs.mkdir(path.join(base, "snapshots"), { recursive: true });
17
+ return base;
18
+ }
19
+ /** Generate draft proposal artifacts — never auto-approved. */
20
+ export async function runProjectInit(rootInput, answers) {
21
+ const root = resolveRepoRoot(rootInput);
22
+ await ensureRepositoryBrainDirs(root);
23
+ const filled = {
24
+ projectName: answers.projectName ?? path.basename(root),
25
+ projectType: answers.projectType ?? "application",
26
+ businessDomain: answers.businessDomain ?? "unknown",
27
+ languages: answers.languages?.length ? answers.languages : ["typescript"],
28
+ frameworks: answers.frameworks ?? [],
29
+ database: answers.database ?? "unknown",
30
+ deploymentModel: answers.deploymentModel ?? "unknown",
31
+ teamStructure: answers.teamStructure ?? "unknown",
32
+ securityRequirements: answers.securityRequirements ?? "local-first; no secrets in VCS",
33
+ preferredArchitecture: answers.preferredArchitecture ?? "modular-monolith",
34
+ aiAgentEnvironments: answers.aiAgentEnvironments?.length
35
+ ? answers.aiAgentEnvironments
36
+ : ["cursor", "claude-code"],
37
+ };
38
+ const docs = [
39
+ {
40
+ kind: "architecture-overview",
41
+ title: "Architecture overview (proposed)",
42
+ body: `# Architecture overview\n\nStatus: **proposed** (not approved)\n\nProject: ${filled.projectName}\nType: ${filled.projectType}\nPreferred architecture: ${filled.preferredArchitecture}\nLanguages: ${filled.languages.join(", ")}\nFrameworks: ${filled.frameworks.join(", ") || "(none specified)"}\n\nThis document is AI/tool-generated scaffolding. Human review required before treating as fact.\n`,
43
+ },
44
+ {
45
+ kind: "business-domain-map",
46
+ title: "Business domain map (proposed)",
47
+ body: `# Business domain map\n\nPrimary domain: ${filled.businessDomain}\n\n- Core domain: ${filled.businessDomain}\n- Supporting domains: (to be filled during review)\n\nStatus: **proposed**\n`,
48
+ },
49
+ {
50
+ kind: "feature-map",
51
+ title: "Feature map (proposed)",
52
+ body: `# Feature map\n\n1. Core product workflows for ${filled.projectName}\n2. Operator / admin surfaces\n3. Integrations\n\nStatus: **proposed**\n`,
53
+ },
54
+ {
55
+ kind: "module-plan",
56
+ title: "Module plan (proposed)",
57
+ body: `# Module plan\n\nSuggested modules (review before creating):\n- \`core\` — domain logic\n- \`api\` — HTTP / CLI surfaces\n- \`infra\` — persistence / adapters\n\nStatus: **proposed**\n`,
58
+ },
59
+ {
60
+ kind: "folder-structure",
61
+ title: "Folder structure proposal",
62
+ body: `# Folder structure\n\n\`\`\`\nsrc/\n core/\n adapters/\n cli/\ntests/\ndocs/\n\`\`\`\n\nStatus: **proposed**\n`,
63
+ },
64
+ {
65
+ kind: "domain-model",
66
+ title: "Domain model (proposed)",
67
+ body: `# Domain model\n\nEntities TBD for domain **${filled.businessDomain}**.\n\nStatus: **proposed**\n`,
68
+ },
69
+ {
70
+ kind: "api-proposal",
71
+ title: "API proposal",
72
+ body: `# API proposal\n\nLocal-first CLI and optional HTTP API. No public cloud assumed.\n\nStatus: **proposed**\n`,
73
+ },
74
+ {
75
+ kind: "data-model",
76
+ title: "Data model proposal",
77
+ body: `# Data model\n\nDatabase preference: ${filled.database}\nDefault local store: filesystem JSON under \`.agentdoctor/\`.\n\nStatus: **proposed**\n`,
78
+ },
79
+ {
80
+ kind: "coding-standards",
81
+ title: "Coding standards (proposed)",
82
+ body: `# Coding standards\n\n- TypeScript strict\n- Deterministic analysis where claimed\n- No secrets in logs\n\nStatus: **proposed**\n`,
83
+ },
84
+ {
85
+ kind: "security-rules",
86
+ title: "Security rules (proposed)",
87
+ body: `# Security rules\n\nRequirements: ${filled.securityRequirements}\nDeployment: ${filled.deploymentModel}\n\nStatus: **proposed**\n`,
88
+ },
89
+ {
90
+ kind: "ai-agent-instructions",
91
+ title: "AI agent instructions (proposed)",
92
+ body: `# AI agent instructions\n\nEnvironments: ${filled.aiAgentEnvironments.join(", ")}\n\n- Prefer Project Brain / MCP evidence over guessing\n- Never treat proposed docs as approved facts\n\nStatus: **proposed**\n`,
93
+ },
94
+ {
95
+ kind: "testing-strategy",
96
+ title: "Testing strategy (proposed)",
97
+ body: `# Testing strategy\n\n- Unit + integration + CLI smoke\n- Hostile input / path traversal cases\n\nStatus: **proposed**\n`,
98
+ },
99
+ {
100
+ kind: "deployment-assumptions",
101
+ title: "Deployment assumptions (proposed)",
102
+ body: `# Deployment assumptions\n\nModel: ${filled.deploymentModel}\nTeam: ${filled.teamStructure}\n\nStatus: **proposed**\n`,
103
+ },
104
+ {
105
+ kind: "adr-0001",
106
+ title: "ADR-0001 Local-first storage",
107
+ body: `# ADR-0001: Local-first storage\n\n## Status\nproposed\n\n## Context\nAgentDoctor must work offline.\n\n## Decision\nFilesystem under \`.agentdoctor/\` is default; SQLite/Postgres optional.\n\n## Consequences\nNo cloud required for core operation.\n`,
108
+ },
109
+ ];
110
+ const artifacts = [];
111
+ const dir = proposalsDir(root);
112
+ for (const doc of docs) {
113
+ const id = `prop_${createHash("sha1").update(doc.kind).digest("hex").slice(0, 10)}`;
114
+ const rel = `${doc.kind}.md`;
115
+ const absolute = path.join(dir, rel);
116
+ if (!isPathInsideRoot(dir, absolute))
117
+ throw new Error("proposal path escape");
118
+ await atomicWriteTextFile(absolute, doc.body);
119
+ artifacts.push({
120
+ id,
121
+ kind: doc.kind,
122
+ title: doc.title,
123
+ path: path.posix.join(".agentdoctor/repository-brain/proposals", rel),
124
+ status: "proposed",
125
+ content: doc.body,
126
+ });
127
+ }
128
+ const meta = {
129
+ version: "2.0",
130
+ createdAt: new Date().toISOString(),
131
+ answers: filled,
132
+ artifactIds: artifacts.map((a) => a.id),
133
+ notice: "All init artifacts are proposed/draft — not approved facts",
134
+ };
135
+ await atomicWriteTextFile(path.join(root, ".agentdoctor", "repository-brain", "init.json"), `${JSON.stringify(meta, null, 2)}\n`);
136
+ return { root, artifacts };
137
+ }
138
+ export async function listProposals(rootInput) {
139
+ const root = resolveRepoRoot(rootInput);
140
+ const dir = proposalsDir(root);
141
+ let files = [];
142
+ try {
143
+ files = (await fs.readdir(dir)).filter((f) => f.endsWith(".md")).sort();
144
+ }
145
+ catch {
146
+ return [];
147
+ }
148
+ const out = [];
149
+ const reviews = await loadReviews(root);
150
+ for (const file of files) {
151
+ const content = await fs.readFile(path.join(dir, file), "utf8");
152
+ const kind = file.replace(/\.md$/, "");
153
+ const id = `prop_${createHash("sha1").update(kind).digest("hex").slice(0, 10)}`;
154
+ const review = reviews.find((r) => r.artifactId === id);
155
+ out.push({
156
+ id,
157
+ kind,
158
+ title: kind,
159
+ path: `.agentdoctor/repository-brain/proposals/${file}`,
160
+ status: review?.status ?? "proposed",
161
+ content,
162
+ });
163
+ }
164
+ return out;
165
+ }
166
+ async function loadReviews(root) {
167
+ try {
168
+ const raw = await fs.readFile(reviewFile(root), "utf8");
169
+ const parsed = JSON.parse(raw);
170
+ return Array.isArray(parsed.items) ? parsed.items : [];
171
+ }
172
+ catch {
173
+ return [];
174
+ }
175
+ }
176
+ export async function reviewProposal(options) {
177
+ const root = resolveRepoRoot(options.root);
178
+ await ensureRepositoryBrainDirs(root);
179
+ const items = await loadReviews(root);
180
+ const item = {
181
+ id: randomUUID(),
182
+ artifactId: options.artifactId,
183
+ status: options.decision,
184
+ decidedBy: options.by ?? "local-reviewer",
185
+ decidedAt: new Date().toISOString(),
186
+ ...(options.note ? { note: options.note } : {}),
187
+ };
188
+ const next = [...items.filter((i) => i.artifactId !== options.artifactId), item];
189
+ await atomicWriteTextFile(reviewFile(root), `${JSON.stringify({ version: "2.0", items: next }, null, 2)}\n`);
190
+ // Keep proposal markdown status in sync with the review decision.
191
+ const proposals = await listProposals(root);
192
+ const match = proposals.find((p) => p.id === options.artifactId);
193
+ if (match) {
194
+ const abs = path.join(root, match.path);
195
+ if (isPathInsideRoot(root, abs)) {
196
+ let content = await fs.readFile(abs, "utf8");
197
+ const statusLine = `Status: **${options.decision}**`;
198
+ if (/Status:\s*\*\*[^*]+\*\*/.test(content)) {
199
+ content = content.replace(/Status:\s*\*\*[^*]+\*\*[^\n]*/g, statusLine);
200
+ }
201
+ else {
202
+ content = `${statusLine}\n\n${content}`;
203
+ }
204
+ // Drop stale "not approved" parenthetical if present after replacement edge cases
205
+ content = content.replace(/\s*\(not approved\)/g, "");
206
+ await atomicWriteTextFile(abs, content.endsWith("\n") ? content : `${content}\n`);
207
+ }
208
+ }
209
+ return item;
210
+ }
211
+ export async function writeBrainProductSnapshot(rootInput) {
212
+ const root = resolveRepoRoot(rootInput);
213
+ await ensureRepositoryBrainDirs(root);
214
+ const proposals = await listProposals(root);
215
+ const id = `rbs_${Date.now().toString(36)}`;
216
+ const snapshot = {
217
+ id,
218
+ version: "2.0",
219
+ createdAt: new Date().toISOString(),
220
+ proposals: proposals.map((p) => ({
221
+ id: p.id,
222
+ kind: p.kind,
223
+ status: p.status,
224
+ path: p.path,
225
+ })),
226
+ notice: "Proposal statuses are human-reviewed when present; otherwise proposed/draft",
227
+ };
228
+ const target = path.join(root, ".agentdoctor", "repository-brain", "snapshots", `${id}.json`);
229
+ await atomicWriteTextFile(target, `${JSON.stringify(snapshot, null, 2)}\n`);
230
+ await atomicWriteTextFile(path.join(root, ".agentdoctor", "repository-brain", "snapshots", "latest.json"), `${JSON.stringify(snapshot, null, 2)}\n`);
231
+ return target;
232
+ }
@@ -0,0 +1,41 @@
1
+ export type ChangeKind = "added" | "modified" | "deleted" | "renamed" | "untracked" | "unknown";
2
+ export interface ChangedFile {
3
+ path: string;
4
+ kind: ChangeKind;
5
+ staged: boolean;
6
+ /** Module / component attribution when Brain evidence matches this path */
7
+ modules?: string[];
8
+ }
9
+ export interface ImpactEdge {
10
+ from: string;
11
+ to: string;
12
+ type: string;
13
+ confidence: number;
14
+ }
15
+ export interface ChangeImpact {
16
+ status: "observed" | "unknown";
17
+ reason: string;
18
+ edges: ImpactEdge[];
19
+ relatedPaths: string[];
20
+ }
21
+ export interface ChangeReport {
22
+ root: string;
23
+ gitAvailable: boolean;
24
+ head: string | null;
25
+ since: string | null;
26
+ files: ChangedFile[];
27
+ summary: {
28
+ added: number;
29
+ modified: number;
30
+ deleted: number;
31
+ renamed: number;
32
+ untracked: number;
33
+ };
34
+ impact?: ChangeImpact;
35
+ limitations: string[];
36
+ }
37
+ export declare function analyzeChanges(options: {
38
+ root: string;
39
+ since?: string;
40
+ impact?: boolean;
41
+ }): Promise<ChangeReport>;
@@ -0,0 +1,223 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { loadLatestBrain } from "../brain-cli/service.js";
4
+ import { resolveRepoRoot } from "../../utils/path.js";
5
+ function runGit(root, args) {
6
+ const result = spawnSync("git", args, {
7
+ cwd: root,
8
+ encoding: "utf8",
9
+ maxBuffer: 8 * 1024 * 1024,
10
+ });
11
+ return {
12
+ ok: result.status === 0,
13
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
14
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
15
+ };
16
+ }
17
+ function parseNameStatus(line, staged) {
18
+ const trimmed = line.trimEnd();
19
+ if (!trimmed) {
20
+ return null;
21
+ }
22
+ const parts = trimmed.split(/\t/);
23
+ const code = parts[0]?.trim() ?? "";
24
+ if (!code) {
25
+ return null;
26
+ }
27
+ const kindChar = code[0] ?? "";
28
+ let kind = "unknown";
29
+ if (kindChar === "A")
30
+ kind = "added";
31
+ else if (kindChar === "M")
32
+ kind = "modified";
33
+ else if (kindChar === "D")
34
+ kind = "deleted";
35
+ else if (kindChar === "R")
36
+ kind = "renamed";
37
+ const filePath = parts.length >= 3 ? (parts[2] ?? parts[1] ?? "") : (parts[1] ?? "");
38
+ if (!filePath) {
39
+ return null;
40
+ }
41
+ return { path: filePath.split(path.sep).join("/"), kind, staged };
42
+ }
43
+ function pathMatches(changed, candidate) {
44
+ const a = changed.replace(/^\.\//, "");
45
+ const b = candidate.replace(/^\.\//, "").split(path.sep).join("/");
46
+ return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`) || b.endsWith(`/${a}`);
47
+ }
48
+ function attributeModules(brain, filePath) {
49
+ const hits = new Set();
50
+ for (const component of brain.components) {
51
+ if (pathMatches(filePath, component.path) || pathMatches(filePath, component.name)) {
52
+ hits.add(component.name || component.id);
53
+ }
54
+ }
55
+ for (const domain of brain.model.domains) {
56
+ for (const p of domain.paths) {
57
+ if (pathMatches(filePath, p)) {
58
+ hits.add(domain.name || domain.id);
59
+ }
60
+ }
61
+ }
62
+ return [...hits].sort();
63
+ }
64
+ function computeImpact(brain, files) {
65
+ if (!brain) {
66
+ return {
67
+ status: "unknown",
68
+ reason: "No Project Brain snapshot available; run agentdoctor brain rebuild",
69
+ edges: [],
70
+ relatedPaths: [],
71
+ };
72
+ }
73
+ const deps = brain.model.dependencies;
74
+ if (!deps || deps.length === 0) {
75
+ return {
76
+ status: "unknown",
77
+ reason: "No observed dependency edges in Project Brain",
78
+ edges: [],
79
+ relatedPaths: [],
80
+ };
81
+ }
82
+ const changed = new Set(files.map((f) => f.path));
83
+ const edges = [];
84
+ const related = new Set();
85
+ for (const dep of deps) {
86
+ const fromHit = [...changed].some((p) => pathMatches(p, dep.from));
87
+ const toHit = [...changed].some((p) => pathMatches(p, dep.to));
88
+ if (fromHit || toHit) {
89
+ edges.push({
90
+ from: dep.from,
91
+ to: dep.to,
92
+ type: dep.type,
93
+ confidence: dep.confidence,
94
+ });
95
+ related.add(dep.from);
96
+ related.add(dep.to);
97
+ }
98
+ }
99
+ if (edges.length === 0) {
100
+ return {
101
+ status: "unknown",
102
+ reason: "No dependency edges touch the changed files (evidence insufficient)",
103
+ edges: [],
104
+ relatedPaths: [],
105
+ };
106
+ }
107
+ return {
108
+ status: "observed",
109
+ reason: `${edges.length} observed dependency edge(s) touch changed files`,
110
+ edges: edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to)),
111
+ relatedPaths: [...related].sort(),
112
+ };
113
+ }
114
+ export async function analyzeChanges(options) {
115
+ const root = resolveRepoRoot(options.root);
116
+ const limitations = [];
117
+ const probe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
118
+ if (!probe.ok || probe.stdout.trim() !== "true") {
119
+ return {
120
+ root,
121
+ gitAvailable: false,
122
+ head: null,
123
+ since: options.since ?? null,
124
+ files: [],
125
+ summary: { added: 0, modified: 0, deleted: 0, renamed: 0, untracked: 0 },
126
+ ...(options.impact
127
+ ? {
128
+ impact: {
129
+ status: "unknown",
130
+ reason: "Git repository not available",
131
+ edges: [],
132
+ relatedPaths: [],
133
+ },
134
+ }
135
+ : {}),
136
+ limitations: ["Git repository not available; change intelligence degraded"],
137
+ };
138
+ }
139
+ const head = runGit(root, ["rev-parse", "HEAD"]);
140
+ const headId = head.ok ? head.stdout.trim() : null;
141
+ const files = [];
142
+ if (options.since) {
143
+ const diff = runGit(root, ["diff", "--name-status", `${options.since}...HEAD`]);
144
+ if (!diff.ok) {
145
+ limitations.push(`Unable to diff since ${options.since}: ${diff.stderr.trim() || "git error"}`);
146
+ }
147
+ else {
148
+ for (const line of diff.stdout.split(/\r?\n/)) {
149
+ const parsed = parseNameStatus(line, false);
150
+ if (parsed)
151
+ files.push(parsed);
152
+ }
153
+ }
154
+ }
155
+ else {
156
+ const staged = runGit(root, ["diff", "--cached", "--name-status"]);
157
+ if (staged.ok) {
158
+ for (const line of staged.stdout.split(/\r?\n/)) {
159
+ const parsed = parseNameStatus(line, true);
160
+ if (parsed)
161
+ files.push(parsed);
162
+ }
163
+ }
164
+ const unstaged = runGit(root, ["diff", "--name-status"]);
165
+ if (unstaged.ok) {
166
+ for (const line of unstaged.stdout.split(/\r?\n/)) {
167
+ const parsed = parseNameStatus(line, false);
168
+ if (parsed)
169
+ files.push(parsed);
170
+ }
171
+ }
172
+ const untracked = runGit(root, ["ls-files", "--others", "--exclude-standard"]);
173
+ if (untracked.ok) {
174
+ for (const line of untracked.stdout.split(/\r?\n/)) {
175
+ const p = line.trim();
176
+ if (p) {
177
+ files.push({ path: p.split(path.sep).join("/"), kind: "untracked", staged: false });
178
+ }
179
+ }
180
+ }
181
+ }
182
+ let brain = null;
183
+ if (options.impact || files.length > 0) {
184
+ try {
185
+ brain = await loadLatestBrain(root);
186
+ }
187
+ catch {
188
+ brain = null;
189
+ }
190
+ }
191
+ if (brain) {
192
+ for (const file of files) {
193
+ const modules = attributeModules(brain, file.path);
194
+ if (modules.length > 0) {
195
+ file.modules = modules;
196
+ }
197
+ }
198
+ }
199
+ else if (options.impact) {
200
+ limitations.push("Project Brain unavailable for module attribution");
201
+ }
202
+ const summary = {
203
+ added: files.filter((f) => f.kind === "added").length,
204
+ modified: files.filter((f) => f.kind === "modified").length,
205
+ deleted: files.filter((f) => f.kind === "deleted").length,
206
+ renamed: files.filter((f) => f.kind === "renamed").length,
207
+ untracked: files.filter((f) => f.kind === "untracked").length,
208
+ };
209
+ const impact = options.impact ? computeImpact(brain, files) : undefined;
210
+ if (!options.impact) {
211
+ limitations.push("Pass --impact to compute observed dependency impact from Project Brain (UNKNOWN when evidence is missing)");
212
+ }
213
+ return {
214
+ root,
215
+ gitAvailable: true,
216
+ head: headId,
217
+ since: options.since ?? null,
218
+ files: files.sort((a, b) => a.path.localeCompare(b.path)),
219
+ summary,
220
+ ...(impact ? { impact } : {}),
221
+ limitations,
222
+ };
223
+ }
@@ -0,0 +1,15 @@
1
+ export type ContextConflictKind = "duplicate-instruction-surface" | "contradictory-tone" | "overlapping-ignore" | "empty-instruction-file" | "oversized-instruction-file" | "invalid-path-reference" | "ambiguous-marker" | "outdated-year-reference" | "terminology-inconsistency" | "unused-ignore-candidate";
2
+ export interface ContextConflict {
3
+ kind: ContextConflictKind;
4
+ severity: "warning" | "info";
5
+ files: string[];
6
+ message: string;
7
+ confidence: "high" | "medium" | "low";
8
+ }
9
+ export interface ContextHealthReport {
10
+ root: string;
11
+ conflicts: ContextConflict[];
12
+ instructionFiles: string[];
13
+ ignoreFiles: string[];
14
+ }
15
+ export declare function analyzeContextHealth(rootInput: string): Promise<ContextHealthReport>;