@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.
- package/CHANGELOG.md +119 -5
- package/README.md +165 -332
- package/dist/agents/aider/adapter.d.ts +1 -0
- package/dist/agents/aider/adapter.js +1 -0
- package/dist/agents/aider/detector.d.ts +14 -0
- package/dist/agents/aider/detector.js +120 -0
- package/dist/agents/copilot/adapter.d.ts +1 -0
- package/dist/agents/copilot/adapter.js +1 -0
- package/dist/agents/copilot/detector.d.ts +13 -0
- package/dist/agents/copilot/detector.js +113 -0
- package/dist/agents/gemini/adapter.d.ts +1 -0
- package/dist/agents/gemini/adapter.js +1 -0
- package/dist/agents/gemini/detector.d.ts +13 -0
- package/dist/agents/gemini/detector.js +151 -0
- package/dist/agents/registry.js +13 -1
- package/dist/agents/types.d.ts +1 -1
- package/dist/agents/windsurf/adapter.d.ts +1 -0
- package/dist/agents/windsurf/adapter.js +1 -0
- package/dist/agents/windsurf/detector.d.ts +14 -0
- package/dist/agents/windsurf/detector.js +119 -0
- package/dist/architecture/c4.d.ts +22 -0
- package/dist/architecture/c4.js +62 -0
- package/dist/cli/commands/brain.d.ts +12 -0
- package/dist/cli/commands/brain.js +162 -0
- package/dist/cli/commands/complete.d.ts +23 -0
- package/dist/cli/commands/complete.js +232 -0
- package/dist/cli/commands/explain.js +1 -1
- package/dist/cli/commands/fix.js +22 -2
- package/dist/cli/commands/mcp.d.ts +8 -0
- package/dist/cli/commands/mcp.js +14 -0
- package/dist/cli/commands/platform.d.ts +15 -0
- package/dist/cli/commands/platform.js +191 -0
- package/dist/cli/commands/v2.d.ts +55 -0
- package/dist/cli/commands/v2.js +278 -0
- package/dist/cli/program.js +809 -2
- package/dist/constants.d.ts +3 -2
- package/dist/constants.js +5 -1
- package/dist/contracts/adapters.d.ts +7 -0
- package/dist/contracts/adapters.js +60 -0
- package/dist/contracts/index.d.ts +162 -0
- package/dist/contracts/index.js +18 -0
- package/dist/core/baseline/store.d.ts +48 -0
- package/dist/core/baseline/store.js +163 -0
- package/dist/core/brain-cli/service.d.ts +26 -0
- package/dist/core/brain-cli/service.js +175 -0
- package/dist/core/brain-product/init.d.ts +45 -0
- package/dist/core/brain-product/init.js +232 -0
- package/dist/core/changes/analyze.d.ts +41 -0
- package/dist/core/changes/analyze.js +223 -0
- package/dist/core/context-health/analyze.d.ts +15 -0
- package/dist/core/context-health/analyze.js +234 -0
- package/dist/core/fix/apply.d.ts +21 -4
- package/dist/core/fix/apply.js +118 -18
- package/dist/core/fix/backup.d.ts +30 -0
- package/dist/core/fix/backup.js +193 -0
- package/dist/core/fix/plan.d.ts +6 -3
- package/dist/core/fix/plan.js +113 -9
- package/dist/core/fix/render.d.ts +2 -0
- package/dist/core/fix/render.js +36 -21
- package/dist/core/fix/run.js +5 -1
- package/dist/core/fix/safe-target.d.ts +14 -0
- package/dist/core/fix/safe-target.js +81 -0
- package/dist/core/fix/types.d.ts +12 -1
- package/dist/core/fix/types.js +2 -0
- package/dist/core/fix/writers/claude-settings.js +3 -1
- package/dist/core/fix/writers/codex-config.d.ts +3 -0
- package/dist/core/fix/writers/codex-config.js +24 -3
- package/dist/core/fix/writers/cursorignore.js +3 -1
- package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
- package/dist/core/fix/writers/simple-ignore.js +68 -0
- package/dist/core/monorepo/detect.d.ts +13 -0
- package/dist/core/monorepo/detect.js +121 -0
- package/dist/core/rules/build-context.js +22 -3
- package/dist/core/rules/context/generated-directory.js +11 -1
- package/dist/core/rules/context/large-instruction-file.js +6 -0
- package/dist/core/rules/context/large-log-file.js +16 -1
- package/dist/core/rules/ignore.d.ts +11 -1
- package/dist/core/rules/ignore.js +22 -1
- package/dist/core/rules/instructions/empty-instructions.js +7 -0
- package/dist/core/rules/security/env-file-exposure.js +23 -2
- package/dist/core/rules/security/private-key-file.js +1 -1
- package/dist/core/scanner/scan.js +1 -1
- package/dist/core/schemas/validate.d.ts +6 -0
- package/dist/core/schemas/validate.js +62 -0
- package/dist/core/scoring/compute-scores.d.ts +1 -1
- package/dist/core/scoring/compute-scores.js +5 -1
- package/dist/core/scoring/placeholder.d.ts +1 -1
- package/dist/core/scoring/placeholder.js +5 -1
- package/dist/core/secrets/scan.d.ts +23 -0
- package/dist/core/secrets/scan.js +172 -0
- package/dist/dashboard/server.d.ts +24 -0
- package/dist/dashboard/server.js +386 -0
- package/dist/enforcement/runner.d.ts +23 -0
- package/dist/enforcement/runner.js +56 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/integrations/github/pr-analyze.d.ts +28 -0
- package/dist/integrations/github/pr-analyze.js +98 -0
- package/dist/integrations/local-ai/provider.d.ts +45 -0
- package/dist/integrations/local-ai/provider.js +104 -0
- package/dist/intelligence/git/analyze.d.ts +34 -0
- package/dist/intelligence/git/analyze.js +136 -0
- package/dist/intelligence/graph/build.d.ts +13 -0
- package/dist/intelligence/graph/build.js +215 -0
- package/dist/knowledge/store.d.ts +25 -0
- package/dist/knowledge/store.js +91 -0
- package/dist/mcp/agentdoctor/server.d.ts +16 -0
- package/dist/mcp/agentdoctor/server.js +67 -0
- package/dist/mcp/brain/session.js +7 -7
- package/dist/mcp/intelligence/handlers.d.ts +11 -0
- package/dist/mcp/intelligence/handlers.js +175 -0
- package/dist/mcp/intelligence/path-safety.d.ts +11 -0
- package/dist/mcp/intelligence/path-safety.js +82 -0
- package/dist/mcp/intelligence/registry.d.ts +8 -0
- package/dist/mcp/intelligence/registry.js +160 -0
- package/dist/ops/health.d.ts +18 -0
- package/dist/ops/health.js +51 -0
- package/dist/platform/ai-quality/analyze.d.ts +9 -0
- package/dist/platform/ai-quality/analyze.js +65 -0
- package/dist/platform/architecture/drift.d.ts +17 -0
- package/dist/platform/architecture/drift.js +74 -0
- package/dist/platform/auth/local.d.ts +15 -0
- package/dist/platform/auth/local.js +58 -0
- package/dist/platform/context-security/analyze.d.ts +6 -0
- package/dist/platform/context-security/analyze.js +97 -0
- package/dist/platform/firewall/evaluate.d.ts +54 -0
- package/dist/platform/firewall/evaluate.js +308 -0
- package/dist/platform/graph/build.d.ts +6 -0
- package/dist/platform/graph/build.js +193 -0
- package/dist/platform/health/analyze.d.ts +7 -0
- package/dist/platform/health/analyze.js +186 -0
- package/dist/platform/index.d.ts +27 -0
- package/dist/platform/index.js +87 -0
- package/dist/platform/knowledge/analyze.d.ts +20 -0
- package/dist/platform/knowledge/analyze.js +96 -0
- package/dist/platform/provenance/build.d.ts +44 -0
- package/dist/platform/provenance/build.js +65 -0
- package/dist/platform/readiness/scorecard.d.ts +5 -0
- package/dist/platform/readiness/scorecard.js +98 -0
- package/dist/platform/refactor/impact.d.ts +20 -0
- package/dist/platform/refactor/impact.js +60 -0
- package/dist/platform/reports/export.d.ts +12 -0
- package/dist/platform/reports/export.js +92 -0
- package/dist/platform/security/redact.d.ts +15 -0
- package/dist/platform/security/redact.js +87 -0
- package/dist/platform/sessions/store.d.ts +32 -0
- package/dist/platform/sessions/store.js +97 -0
- package/dist/platform/store.d.ts +6 -0
- package/dist/platform/store.js +57 -0
- package/dist/platform/test-impact/analyze.d.ts +32 -0
- package/dist/platform/test-impact/analyze.js +164 -0
- package/dist/platform/time-machine/compare.d.ts +17 -0
- package/dist/platform/time-machine/compare.js +62 -0
- package/dist/platform/tokens/plan.d.ts +28 -0
- package/dist/platform/tokens/plan.js +89 -0
- package/dist/platform/types.d.ts +111 -0
- package/dist/platform/types.js +5 -0
- package/dist/plugins/runtime.d.ts +36 -0
- package/dist/plugins/runtime.js +101 -0
- package/dist/plugins/sdk.d.ts +29 -0
- package/dist/plugins/sdk.js +114 -0
- package/dist/policy/packs.d.ts +9 -0
- package/dist/policy/packs.js +59 -0
- package/dist/reporters/terminal/report.js +38 -5
- package/dist/storage/provider.d.ts +35 -0
- package/dist/storage/provider.js +103 -0
- package/dist/team/auth.d.ts +35 -0
- package/dist/team/auth.js +70 -0
- package/dist/types/index.d.ts +5 -1
- package/package.json +14 -7
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { EXIT_CODES } from "../../types/index.js";
|
|
3
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
4
|
+
import { analyzeRenameImpact, analyzeTestImpact, appendSessionEvent, buildProvenance, compareCommits, createSession, endSession, evaluateAgentAction, exportSessionMarkdown, formatTestImpactHuman, listSessions, loadSession, persistTestImpactReport, planContext, runPlatformScan, saveProvenance, EVALUATE_ONLY, } from "../../platform/index.js";
|
|
5
|
+
import { buildRepositoryGraph } from "../../platform/graph/build.js";
|
|
6
|
+
function printJson(value) {
|
|
7
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
8
|
+
}
|
|
9
|
+
export async function runPlatformCommand(options) {
|
|
10
|
+
const root = resolveRepoRoot(options.root ?? process.cwd());
|
|
11
|
+
try {
|
|
12
|
+
switch (options.action) {
|
|
13
|
+
case "scan": {
|
|
14
|
+
const result = await runPlatformScan(root);
|
|
15
|
+
if (options.json)
|
|
16
|
+
printJson({
|
|
17
|
+
findingCount: result.snapshot.findings.length,
|
|
18
|
+
nodeCount: result.snapshot.graph.nodes.length,
|
|
19
|
+
testImpact: result.snapshot.testImpact,
|
|
20
|
+
readiness: result.snapshot.readiness.categories.map((c) => ({
|
|
21
|
+
id: c.id,
|
|
22
|
+
score: c.score,
|
|
23
|
+
})),
|
|
24
|
+
reports: result.reportPaths,
|
|
25
|
+
limitations: result.snapshot.limitations,
|
|
26
|
+
});
|
|
27
|
+
else {
|
|
28
|
+
process.stdout.write(`AgentDoctor 2.0 platform scan\n findings: ${result.snapshot.findings.length}\n graph nodes: ${result.snapshot.graph.nodes.length}\n test-impact recommendations: ${result.testImpact.recommendedTests.length}\n reports: ${Object.keys(result.reportPaths).join(", ")}\n`);
|
|
29
|
+
}
|
|
30
|
+
return EXIT_CODES.SUCCESS;
|
|
31
|
+
}
|
|
32
|
+
case "graph": {
|
|
33
|
+
const graph = await buildRepositoryGraph(root);
|
|
34
|
+
if (options.json)
|
|
35
|
+
printJson(graph);
|
|
36
|
+
else
|
|
37
|
+
process.stdout.write(`Graph nodes=${graph.nodes.length} edges=${graph.edges.length}\n`);
|
|
38
|
+
return EXIT_CODES.SUCCESS;
|
|
39
|
+
}
|
|
40
|
+
case "test-impact": {
|
|
41
|
+
const report = await analyzeTestImpact(root);
|
|
42
|
+
const out = await persistTestImpactReport(root, report);
|
|
43
|
+
if (options.json)
|
|
44
|
+
printJson({ ...report, reportPath: out });
|
|
45
|
+
else {
|
|
46
|
+
process.stdout.write(formatTestImpactHuman(report));
|
|
47
|
+
process.stdout.write(` wrote: ${out}\n`);
|
|
48
|
+
}
|
|
49
|
+
return EXIT_CODES.SUCCESS;
|
|
50
|
+
}
|
|
51
|
+
case "firewall-check":
|
|
52
|
+
case "policy-check": {
|
|
53
|
+
const verdict = await evaluateAgentAction(root, {
|
|
54
|
+
actionId: randomUUID(),
|
|
55
|
+
agentId: "cli",
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
type: options.actionType ?? "shell",
|
|
58
|
+
params: {
|
|
59
|
+
...(options.command ? { command: options.command } : {}),
|
|
60
|
+
...(options.path ? { path: options.path } : {}),
|
|
61
|
+
},
|
|
62
|
+
repositoryRoot: root,
|
|
63
|
+
}, { failClosed: options.failClosed === true });
|
|
64
|
+
if (options.json)
|
|
65
|
+
printJson({ ...verdict, notice: EVALUATE_ONLY });
|
|
66
|
+
else
|
|
67
|
+
process.stdout.write(`Action Policy Evaluator\n ${EVALUATE_ONLY}\n decision=${verdict.decision} risk=${verdict.riskLevel} reason=${verdict.reason} executed=${verdict.executionResult}\n`);
|
|
68
|
+
return EXIT_CODES.SUCCESS;
|
|
69
|
+
}
|
|
70
|
+
case "session-start": {
|
|
71
|
+
const session = await createSession({ root, agentId: "cli" });
|
|
72
|
+
process.stdout.write(`session ${session.id}\n`);
|
|
73
|
+
return EXIT_CODES.SUCCESS;
|
|
74
|
+
}
|
|
75
|
+
case "session-show": {
|
|
76
|
+
if (!options.sessionId) {
|
|
77
|
+
console.error("Error: --session <id> required");
|
|
78
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
79
|
+
}
|
|
80
|
+
const session = await loadSession(root, options.sessionId);
|
|
81
|
+
if (!session) {
|
|
82
|
+
console.error("Error: session not found");
|
|
83
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
84
|
+
}
|
|
85
|
+
if (options.json)
|
|
86
|
+
printJson(session);
|
|
87
|
+
else
|
|
88
|
+
process.stdout.write(exportSessionMarkdown(session));
|
|
89
|
+
return EXIT_CODES.SUCCESS;
|
|
90
|
+
}
|
|
91
|
+
case "session-list": {
|
|
92
|
+
const ids = await listSessions(root);
|
|
93
|
+
if (options.json)
|
|
94
|
+
printJson({ sessions: ids });
|
|
95
|
+
else
|
|
96
|
+
process.stdout.write(ids.length ? `${ids.join("\n")}\n` : "No sessions.\n");
|
|
97
|
+
return EXIT_CODES.SUCCESS;
|
|
98
|
+
}
|
|
99
|
+
case "provenance": {
|
|
100
|
+
if (!options.path) {
|
|
101
|
+
console.error("Error: --path <file> required");
|
|
102
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
103
|
+
}
|
|
104
|
+
const record = buildProvenance({ root, file: options.path });
|
|
105
|
+
const out = await saveProvenance(root, record);
|
|
106
|
+
if (options.json)
|
|
107
|
+
printJson(record);
|
|
108
|
+
else
|
|
109
|
+
process.stdout.write(`Wrote ${out}\n`);
|
|
110
|
+
return EXIT_CODES.SUCCESS;
|
|
111
|
+
}
|
|
112
|
+
case "context": {
|
|
113
|
+
const graph = await buildRepositoryGraph(root);
|
|
114
|
+
const plan = await planContext({
|
|
115
|
+
root,
|
|
116
|
+
graph,
|
|
117
|
+
query: options.query ?? "src",
|
|
118
|
+
});
|
|
119
|
+
if (options.json)
|
|
120
|
+
printJson(plan);
|
|
121
|
+
else
|
|
122
|
+
process.stdout.write(`tokens=${plan.estimatedTokens}/${plan.budgetTokens} selected=${plan.selected.length}\n`);
|
|
123
|
+
return EXIT_CODES.SUCCESS;
|
|
124
|
+
}
|
|
125
|
+
case "refactor": {
|
|
126
|
+
if (!options.symbol) {
|
|
127
|
+
console.error("Error: --symbol required");
|
|
128
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
129
|
+
}
|
|
130
|
+
const graph = await buildRepositoryGraph(root);
|
|
131
|
+
const impact = await analyzeRenameImpact({ root, symbol: options.symbol, graph });
|
|
132
|
+
if (options.json)
|
|
133
|
+
printJson(impact);
|
|
134
|
+
else
|
|
135
|
+
process.stdout.write(`symbol=${impact.symbol} affected=${impact.affectedFiles.length} risk=${impact.risk}\n`);
|
|
136
|
+
return EXIT_CODES.SUCCESS;
|
|
137
|
+
}
|
|
138
|
+
case "time-machine": {
|
|
139
|
+
if (!options.left || !options.right) {
|
|
140
|
+
console.error("Error: --left and --right refs required");
|
|
141
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
142
|
+
}
|
|
143
|
+
const cmp = await compareCommits({
|
|
144
|
+
root,
|
|
145
|
+
left: options.left,
|
|
146
|
+
right: options.right,
|
|
147
|
+
});
|
|
148
|
+
if (options.json)
|
|
149
|
+
printJson(cmp);
|
|
150
|
+
else
|
|
151
|
+
process.stdout.write(`${cmp.summary}\n`);
|
|
152
|
+
return EXIT_CODES.SUCCESS;
|
|
153
|
+
}
|
|
154
|
+
case "demo-session": {
|
|
155
|
+
const session = await createSession({ root, agentId: "demo-agent", model: "unknown" });
|
|
156
|
+
await appendSessionEvent(session, {
|
|
157
|
+
type: "tool-call",
|
|
158
|
+
summary: "Proposed shell: npm test",
|
|
159
|
+
detail: { command: "npm test" },
|
|
160
|
+
risk: "low",
|
|
161
|
+
});
|
|
162
|
+
const verdict = await evaluateAgentAction(root, {
|
|
163
|
+
actionId: randomUUID(),
|
|
164
|
+
agentId: "demo-agent",
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
type: "shell",
|
|
167
|
+
params: { command: "npm test" },
|
|
168
|
+
repositoryRoot: root,
|
|
169
|
+
});
|
|
170
|
+
await appendSessionEvent(session, {
|
|
171
|
+
type: "policy",
|
|
172
|
+
summary: `Action Policy Evaluator ${verdict.decision}: ${verdict.reason}`,
|
|
173
|
+
risk: verdict.riskLevel === "critical" ? "critical" : "low",
|
|
174
|
+
});
|
|
175
|
+
await endSession(session);
|
|
176
|
+
if (options.json)
|
|
177
|
+
printJson({ sessionId: session.id, verdict, notice: EVALUATE_ONLY });
|
|
178
|
+
else
|
|
179
|
+
process.stdout.write(`demo session ${session.id} (${EVALUATE_ONLY})\n`);
|
|
180
|
+
return EXIT_CODES.SUCCESS;
|
|
181
|
+
}
|
|
182
|
+
default:
|
|
183
|
+
console.error(`Error: unknown platform action: ${options.action}`);
|
|
184
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
189
|
+
return EXIT_CODES.INTERNAL_ERROR;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ExitCode } from "../../types/index.js";
|
|
2
|
+
export declare function runChangesCommand(options: {
|
|
3
|
+
root?: string;
|
|
4
|
+
since?: string;
|
|
5
|
+
impact?: boolean;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
}): Promise<ExitCode>;
|
|
8
|
+
export declare function runContextHealthCommand(options: {
|
|
9
|
+
root?: string;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}): Promise<ExitCode>;
|
|
12
|
+
export declare function runSecretsCommand(options: {
|
|
13
|
+
root?: string;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}): Promise<ExitCode>;
|
|
16
|
+
export declare function runFixUndoCommand(options: {
|
|
17
|
+
root?: string;
|
|
18
|
+
auditId: string;
|
|
19
|
+
}): Promise<ExitCode>;
|
|
20
|
+
export declare function runFixHistoryCommand(options: {
|
|
21
|
+
root?: string;
|
|
22
|
+
json?: boolean;
|
|
23
|
+
}): Promise<ExitCode>;
|
|
24
|
+
export declare function runBaselineCommand(options: {
|
|
25
|
+
action: "save" | "list" | "diff" | "delete" | "trends";
|
|
26
|
+
name?: string;
|
|
27
|
+
root?: string;
|
|
28
|
+
json?: boolean;
|
|
29
|
+
}): Promise<ExitCode>;
|
|
30
|
+
export declare function runPackagesCommand(options: {
|
|
31
|
+
root?: string;
|
|
32
|
+
json?: boolean;
|
|
33
|
+
scan?: boolean;
|
|
34
|
+
}): Promise<ExitCode>;
|
|
35
|
+
export declare function runPrReviewCommand(options: {
|
|
36
|
+
root?: string;
|
|
37
|
+
base?: string;
|
|
38
|
+
json?: boolean;
|
|
39
|
+
}): Promise<ExitCode>;
|
|
40
|
+
export declare function runDashboardCommand(options: {
|
|
41
|
+
root?: string;
|
|
42
|
+
host?: string;
|
|
43
|
+
port?: number;
|
|
44
|
+
allowNonLoopback?: boolean;
|
|
45
|
+
}): Promise<ExitCode>;
|
|
46
|
+
export declare function runPluginsCommand(options: {
|
|
47
|
+
root?: string;
|
|
48
|
+
json?: boolean;
|
|
49
|
+
run?: boolean;
|
|
50
|
+
}): Promise<ExitCode>;
|
|
51
|
+
export declare function runLocalAiCommand(options: {
|
|
52
|
+
provider?: string;
|
|
53
|
+
prompt?: string;
|
|
54
|
+
json?: boolean;
|
|
55
|
+
}): Promise<ExitCode>;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { EXIT_CODES } from "../../types/index.js";
|
|
2
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
3
|
+
import { analyzeChanges } from "../../core/changes/analyze.js";
|
|
4
|
+
import { analyzeContextHealth } from "../../core/context-health/analyze.js";
|
|
5
|
+
import { scanSecrets } from "../../core/secrets/scan.js";
|
|
6
|
+
import { listFixAudits, undoFix } from "../../core/fix/backup.js";
|
|
7
|
+
import { baselineFromScan, compareToBaseline, computeBaselineTrends, deleteNamedBaseline, listNamedBaselines, loadNamedBaseline, saveNamedBaseline, } from "../../core/baseline/store.js";
|
|
8
|
+
import { detectMonorepo } from "../../core/monorepo/detect.js";
|
|
9
|
+
import { analyzePullRequest } from "../../integrations/github/pr-analyze.js";
|
|
10
|
+
import { startDashboardServer } from "../../dashboard/server.js";
|
|
11
|
+
import { discoverPlugins } from "../../plugins/sdk.js";
|
|
12
|
+
import { runPluginAnalyzers } from "../../plugins/runtime.js";
|
|
13
|
+
import { createLocalAiProvider, } from "../../integrations/local-ai/provider.js";
|
|
14
|
+
import { scan } from "../../core/scanner/scan.js";
|
|
15
|
+
function printJson(value) {
|
|
16
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
17
|
+
}
|
|
18
|
+
export async function runChangesCommand(options) {
|
|
19
|
+
const root = resolveRepoRoot(options.root ?? process.cwd());
|
|
20
|
+
const report = await analyzeChanges({
|
|
21
|
+
root,
|
|
22
|
+
...(options.since ? { since: options.since } : {}),
|
|
23
|
+
impact: options.impact === true,
|
|
24
|
+
});
|
|
25
|
+
if (options.json) {
|
|
26
|
+
printJson(report);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
process.stdout.write(`Changes (${report.gitAvailable ? "git" : "no-git"})\n files: ${report.files.length}\n added: ${report.summary.added} modified: ${report.summary.modified} deleted: ${report.summary.deleted}\n`);
|
|
30
|
+
for (const f of report.files.slice(0, 40)) {
|
|
31
|
+
const mods = f.modules?.length ? ` modules=${f.modules.join(",")}` : "";
|
|
32
|
+
process.stdout.write(` ${f.staged ? "S" : " "} ${f.kind.padEnd(10)} ${f.path}${mods}\n`);
|
|
33
|
+
}
|
|
34
|
+
if (report.impact) {
|
|
35
|
+
process.stdout.write(`Impact: ${report.impact.status} — ${report.impact.reason}\n edges: ${report.impact.edges.length}\n`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return EXIT_CODES.SUCCESS;
|
|
39
|
+
}
|
|
40
|
+
export async function runContextHealthCommand(options) {
|
|
41
|
+
const root = resolveRepoRoot(options.root ?? process.cwd());
|
|
42
|
+
const report = await analyzeContextHealth(root);
|
|
43
|
+
if (options.json)
|
|
44
|
+
printJson(report);
|
|
45
|
+
else {
|
|
46
|
+
process.stdout.write(`Context health\n instruction files: ${report.instructionFiles.length}\n conflicts: ${report.conflicts.length}\n`);
|
|
47
|
+
for (const c of report.conflicts) {
|
|
48
|
+
process.stdout.write(` [${c.severity}] ${c.kind}: ${c.message}\n`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return EXIT_CODES.SUCCESS;
|
|
52
|
+
}
|
|
53
|
+
export async function runSecretsCommand(options) {
|
|
54
|
+
const root = resolveRepoRoot(options.root ?? process.cwd());
|
|
55
|
+
const report = await scanSecrets({ root, enabled: true });
|
|
56
|
+
if (options.json)
|
|
57
|
+
printJson(report);
|
|
58
|
+
else {
|
|
59
|
+
process.stdout.write(`Secret scan (opt-in)\n files: ${report.filesScanned}\n findings: ${report.findings.length}\n`);
|
|
60
|
+
for (const f of report.findings.slice(0, 30)) {
|
|
61
|
+
process.stdout.write(` [${f.severity}] ${f.ruleId} ${f.file}:${f.line} ${f.redactedSnippet}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return report.findings.some((f) => f.severity === "critical")
|
|
65
|
+
? EXIT_CODES.ISSUES_OR_THRESHOLD
|
|
66
|
+
: EXIT_CODES.SUCCESS;
|
|
67
|
+
}
|
|
68
|
+
export async function runFixUndoCommand(options) {
|
|
69
|
+
try {
|
|
70
|
+
const record = await undoFix({
|
|
71
|
+
root: resolveRepoRoot(options.root ?? process.cwd()),
|
|
72
|
+
auditId: options.auditId,
|
|
73
|
+
});
|
|
74
|
+
process.stdout.write(`Undid Safe Fix audit ${options.auditId} → undo audit ${record.id}\n`);
|
|
75
|
+
return EXIT_CODES.SUCCESS;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
return EXIT_CODES.INTERNAL_ERROR;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export async function runFixHistoryCommand(options) {
|
|
83
|
+
const audits = await listFixAudits(resolveRepoRoot(options.root ?? process.cwd()));
|
|
84
|
+
if (options.json)
|
|
85
|
+
printJson({ audits });
|
|
86
|
+
else if (audits.length === 0)
|
|
87
|
+
process.stdout.write("No Safe Fix audits yet.\n");
|
|
88
|
+
else {
|
|
89
|
+
for (const a of audits) {
|
|
90
|
+
process.stdout.write(`${a.id} ${a.mode} ${a.createdAt} entries=${a.entries.length}\n`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return EXIT_CODES.SUCCESS;
|
|
94
|
+
}
|
|
95
|
+
export async function runBaselineCommand(options) {
|
|
96
|
+
const root = resolveRepoRoot(options.root ?? process.cwd());
|
|
97
|
+
try {
|
|
98
|
+
if (options.action === "list") {
|
|
99
|
+
const names = await listNamedBaselines(root);
|
|
100
|
+
if (options.json)
|
|
101
|
+
printJson({ baselines: names });
|
|
102
|
+
else
|
|
103
|
+
process.stdout.write(names.length ? `${names.join("\n")}\n` : "No named baselines.\n");
|
|
104
|
+
return EXIT_CODES.SUCCESS;
|
|
105
|
+
}
|
|
106
|
+
if (options.action === "trends") {
|
|
107
|
+
const trends = await computeBaselineTrends(root);
|
|
108
|
+
if (options.json)
|
|
109
|
+
printJson(trends);
|
|
110
|
+
else if (trends.points.length === 0) {
|
|
111
|
+
process.stdout.write("No baselines for trends.\n");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
for (const p of trends.points) {
|
|
115
|
+
process.stdout.write(`${p.createdAt} ${p.name} total=${p.counts.total} critical=${p.counts.critical}\n`);
|
|
116
|
+
}
|
|
117
|
+
for (const d of trends.deltas) {
|
|
118
|
+
process.stdout.write(` Δ ${d.from}→${d.to}: total ${d.totalDelta >= 0 ? "+" : ""}${d.totalDelta}, critical ${d.criticalDelta >= 0 ? "+" : ""}${d.criticalDelta}\n`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return EXIT_CODES.SUCCESS;
|
|
122
|
+
}
|
|
123
|
+
if (options.action === "save") {
|
|
124
|
+
if (!options.name) {
|
|
125
|
+
console.error("Error: --name required");
|
|
126
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
127
|
+
}
|
|
128
|
+
const result = await scan({ cwd: root });
|
|
129
|
+
const baseline = baselineFromScan(options.name, result);
|
|
130
|
+
const file = await saveNamedBaseline(root, baseline);
|
|
131
|
+
process.stdout.write(`Saved baseline ${options.name} → ${file}\n`);
|
|
132
|
+
return EXIT_CODES.SUCCESS;
|
|
133
|
+
}
|
|
134
|
+
if (options.action === "delete") {
|
|
135
|
+
if (!options.name) {
|
|
136
|
+
console.error("Error: --name required");
|
|
137
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
138
|
+
}
|
|
139
|
+
await deleteNamedBaseline(root, options.name);
|
|
140
|
+
process.stdout.write(`Deleted baseline ${options.name}\n`);
|
|
141
|
+
return EXIT_CODES.SUCCESS;
|
|
142
|
+
}
|
|
143
|
+
if (options.action === "diff") {
|
|
144
|
+
if (!options.name) {
|
|
145
|
+
console.error("Error: --name required");
|
|
146
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
147
|
+
}
|
|
148
|
+
const baseline = await loadNamedBaseline(root, options.name);
|
|
149
|
+
if (!baseline) {
|
|
150
|
+
console.error(`Error: baseline not found: ${options.name}`);
|
|
151
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
152
|
+
}
|
|
153
|
+
const result = await scan({ cwd: root });
|
|
154
|
+
const diff = compareToBaseline(baseline, result.findings);
|
|
155
|
+
if (options.json)
|
|
156
|
+
printJson(diff);
|
|
157
|
+
else {
|
|
158
|
+
process.stdout.write(`Baseline ${diff.name}\n added: ${diff.added.length}\n removed: ${diff.removed.length}\n recurring: ${diff.recurring.length}\n unchanged: ${diff.unchanged}\n`);
|
|
159
|
+
}
|
|
160
|
+
return EXIT_CODES.SUCCESS;
|
|
161
|
+
}
|
|
162
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
166
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export async function runPackagesCommand(options) {
|
|
170
|
+
const root = resolveRepoRoot(options.root ?? process.cwd());
|
|
171
|
+
const mono = await detectMonorepo(root);
|
|
172
|
+
if (!options.scan) {
|
|
173
|
+
if (options.json)
|
|
174
|
+
printJson(mono);
|
|
175
|
+
else {
|
|
176
|
+
process.stdout.write(`Monorepo: ${mono.isMonorepo ? "yes" : "no"} (${mono.tool})\n packages: ${mono.packages.length}\n`);
|
|
177
|
+
for (const p of mono.packages) {
|
|
178
|
+
process.stdout.write(` - ${p.name} (${p.relativePath})\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return EXIT_CODES.SUCCESS;
|
|
182
|
+
}
|
|
183
|
+
const packageScans = [];
|
|
184
|
+
for (const pkg of mono.packages) {
|
|
185
|
+
const result = await scan({ cwd: pkg.absolutePath });
|
|
186
|
+
packageScans.push({
|
|
187
|
+
name: pkg.name,
|
|
188
|
+
path: pkg.relativePath,
|
|
189
|
+
summary: result.summary,
|
|
190
|
+
overall: result.scores?.overall ?? null,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (options.json)
|
|
194
|
+
printJson({ monorepo: mono, packageScans });
|
|
195
|
+
else {
|
|
196
|
+
for (const p of packageScans) {
|
|
197
|
+
process.stdout.write(`${p.name}: score=${p.overall} findings=${p.summary.total} (c=${p.summary.critical} w=${p.summary.warning})\n`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return EXIT_CODES.SUCCESS;
|
|
201
|
+
}
|
|
202
|
+
export async function runPrReviewCommand(options) {
|
|
203
|
+
const report = await analyzePullRequest({
|
|
204
|
+
root: resolveRepoRoot(options.root ?? process.cwd()),
|
|
205
|
+
dryRun: true,
|
|
206
|
+
...(options.base ? { baseRef: options.base } : {}),
|
|
207
|
+
});
|
|
208
|
+
if (options.json)
|
|
209
|
+
printJson(report);
|
|
210
|
+
else {
|
|
211
|
+
process.stdout.write(report.commentMarkdown);
|
|
212
|
+
process.stdout.write("\n(posted=false; local dry-run only)\n");
|
|
213
|
+
}
|
|
214
|
+
return EXIT_CODES.SUCCESS;
|
|
215
|
+
}
|
|
216
|
+
export async function runDashboardCommand(options) {
|
|
217
|
+
try {
|
|
218
|
+
const server = await startDashboardServer({
|
|
219
|
+
root: resolveRepoRoot(options.root ?? process.cwd()),
|
|
220
|
+
host: options.host ?? "127.0.0.1",
|
|
221
|
+
port: options.port ?? 8787,
|
|
222
|
+
readOnly: true,
|
|
223
|
+
allowNonLoopback: options.allowNonLoopback === true,
|
|
224
|
+
});
|
|
225
|
+
if (options.allowNonLoopback) {
|
|
226
|
+
process.stderr.write("WARNING: dashboard bound outside loopback with --allow-non-loopback. Local ?user= roles are not authentication.\n");
|
|
227
|
+
}
|
|
228
|
+
process.stdout.write(`AgentDoctor dashboard (read-only) at http://${server.host}:${server.port}/\nLocal ?user= is not authentication. Press Ctrl+C to stop.\n`);
|
|
229
|
+
await new Promise(() => {
|
|
230
|
+
/* keep process alive until signal */
|
|
231
|
+
});
|
|
232
|
+
return EXIT_CODES.SUCCESS;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
236
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export async function runPluginsCommand(options) {
|
|
240
|
+
const root = resolveRepoRoot(options.root ?? process.cwd());
|
|
241
|
+
const plugins = await discoverPlugins(root);
|
|
242
|
+
const analyzerResults = options.run ? await runPluginAnalyzers(root) : undefined;
|
|
243
|
+
if (options.json) {
|
|
244
|
+
printJson({ plugins, ...(analyzerResults ? { analyzerResults } : {}) });
|
|
245
|
+
}
|
|
246
|
+
else if (plugins.length === 0) {
|
|
247
|
+
process.stdout.write("No plugins discovered under .agentdoctor/plugins/\n");
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
for (const p of plugins) {
|
|
251
|
+
process.stdout.write(`${p.ok ? "ok" : "ERR"} ${p.manifest.id}@${p.manifest.version} caps=${p.manifest.capabilities.join(",")}\n`);
|
|
252
|
+
for (const e of p.errors)
|
|
253
|
+
process.stdout.write(` ${e}\n`);
|
|
254
|
+
}
|
|
255
|
+
if (analyzerResults) {
|
|
256
|
+
for (const r of analyzerResults) {
|
|
257
|
+
process.stdout.write(`run ${r.ok ? "ok" : "ERR"} ${r.pluginId} notes=${r.notes.length}${r.error ? ` ${r.error}` : ""}\n`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return EXIT_CODES.SUCCESS;
|
|
262
|
+
}
|
|
263
|
+
export async function runLocalAiCommand(options) {
|
|
264
|
+
const id = (options.provider ?? "none");
|
|
265
|
+
const provider = createLocalAiProvider(id === "mock" || id === "ollama" || id === "none" ? id : "none");
|
|
266
|
+
const response = await provider.generate({
|
|
267
|
+
prompt: options.prompt ?? "Summarize repository agent readiness.",
|
|
268
|
+
context: [],
|
|
269
|
+
maxTokens: 128,
|
|
270
|
+
timeoutMs: 3000,
|
|
271
|
+
});
|
|
272
|
+
if (options.json)
|
|
273
|
+
printJson(response);
|
|
274
|
+
else {
|
|
275
|
+
process.stdout.write(`provider=${response.provider} aiGenerated=${response.aiGenerated}\n${response.text || response.error || "(empty)"}\n`);
|
|
276
|
+
}
|
|
277
|
+
return EXIT_CODES.SUCCESS;
|
|
278
|
+
}
|