@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
package/dist/core/fix/plan.js
CHANGED
|
@@ -16,14 +16,22 @@ function claudeConfigured(result) {
|
|
|
16
16
|
function codexConfigured(result) {
|
|
17
17
|
return result.agents.some((a) => a.id === "codex" && (a.detected || a.configured));
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
function geminiConfigured(result) {
|
|
20
|
+
return result.agents.some((a) => a.id === "gemini-cli" && (a.detected || a.configured));
|
|
21
|
+
}
|
|
22
|
+
function aiderConfigured(result) {
|
|
23
|
+
return result.agents.some((a) => a.id === "aider" && (a.detected || a.configured));
|
|
24
|
+
}
|
|
25
|
+
async function loadIgnorePatterns(root, relativePath) {
|
|
26
|
+
const text = await readTextFile(path.join(root, relativePath), DEFAULT_MAX_IGNORE_BYTES);
|
|
22
27
|
if (text === null) {
|
|
23
28
|
return [];
|
|
24
29
|
}
|
|
25
30
|
return parseIgnoreFile(text);
|
|
26
31
|
}
|
|
32
|
+
async function loadCursorignorePatterns(root) {
|
|
33
|
+
return loadIgnorePatterns(root, ".cursorignore");
|
|
34
|
+
}
|
|
27
35
|
async function loadClaudeSettingsText(root) {
|
|
28
36
|
return readTextFile(path.join(root, ".claude/settings.json"), DEFAULT_MAX_IGNORE_BYTES);
|
|
29
37
|
}
|
|
@@ -32,22 +40,29 @@ async function loadCodexConfigText(root) {
|
|
|
32
40
|
}
|
|
33
41
|
/**
|
|
34
42
|
* Build a fix plan from a scan result.
|
|
35
|
-
* Safe context exclusions: Cursor `.cursorignore`, Claude Code Read deny, Codex filesystem deny
|
|
43
|
+
* Safe context exclusions: Cursor `.cursorignore`, Claude Code Read deny, Codex filesystem deny,
|
|
44
|
+
* Gemini CLI `.geminiignore`, Aider `.aiderignore`.
|
|
36
45
|
*/
|
|
37
46
|
export async function buildFixPlan(result) {
|
|
38
47
|
const root = result.repository.root;
|
|
39
48
|
const skipped = [];
|
|
40
49
|
const actionsByKey = new Map();
|
|
41
50
|
const existingCursorPatterns = await loadCursorignorePatterns(root);
|
|
51
|
+
const existingGeminiPatterns = await loadIgnorePatterns(root, ".geminiignore");
|
|
52
|
+
const existingAiderPatterns = await loadIgnorePatterns(root, ".aiderignore");
|
|
42
53
|
const cursorIndex = createIgnoreIndex({
|
|
43
54
|
gitignorePatterns: [],
|
|
44
55
|
cursorignorePatterns: existingCursorPatterns,
|
|
56
|
+
geminiignorePatterns: existingGeminiPatterns,
|
|
57
|
+
aiderignorePatterns: existingAiderPatterns,
|
|
45
58
|
});
|
|
46
59
|
const claudeSettingsText = await loadClaudeSettingsText(root);
|
|
47
60
|
const codexConfigText = await loadCodexConfigText(root);
|
|
48
61
|
const hasCursor = cursorConfigured(result);
|
|
49
62
|
const hasClaude = claudeConfigured(result);
|
|
50
63
|
const hasCodex = codexConfigured(result);
|
|
64
|
+
const hasGemini = geminiConfigured(result);
|
|
65
|
+
const hasAider = aiderConfigured(result);
|
|
51
66
|
for (const finding of result.findings) {
|
|
52
67
|
if (finding.fixability !== "safe") {
|
|
53
68
|
skipped.push({
|
|
@@ -82,6 +97,8 @@ export async function buildFixPlan(result) {
|
|
|
82
97
|
const wantsCursor = finding.affectedAgents.includes("cursor");
|
|
83
98
|
const wantsClaude = finding.affectedAgents.includes("claude-code");
|
|
84
99
|
const wantsCodex = finding.affectedAgents.includes("codex");
|
|
100
|
+
const wantsGemini = finding.affectedAgents.includes("gemini-cli");
|
|
101
|
+
const wantsAider = finding.affectedAgents.includes("aider");
|
|
85
102
|
if (wantsCursor && hasCursor) {
|
|
86
103
|
if (cursorIndex.matchesCursorignore(evidencePath) ||
|
|
87
104
|
cursorIndex.matchesCursorignore(pattern)) {
|
|
@@ -132,8 +149,6 @@ export async function buildFixPlan(result) {
|
|
|
132
149
|
});
|
|
133
150
|
}
|
|
134
151
|
else if (codexConfigText) {
|
|
135
|
-
// Refuse unwritable/invalid config the same way Claude Fix refuses bad JSON —
|
|
136
|
-
// do not silently skip and leave Codex findings uncleared after a partial Cursor write.
|
|
137
152
|
assertWritableCodexConfig(codexConfigText);
|
|
138
153
|
mergeCodexAction(actionsByKey, finding, pattern, evidencePath, denyKey);
|
|
139
154
|
}
|
|
@@ -148,8 +163,75 @@ export async function buildFixPlan(result) {
|
|
|
148
163
|
reason: "Codex not detected; skipping config.toml deny fix",
|
|
149
164
|
});
|
|
150
165
|
}
|
|
166
|
+
if (wantsGemini && hasGemini) {
|
|
167
|
+
if (cursorIndex.matchesGeminiignore(evidencePath) ||
|
|
168
|
+
cursorIndex.matchesGeminiignore(pattern)) {
|
|
169
|
+
skipped.push({
|
|
170
|
+
findingId: finding.id,
|
|
171
|
+
ruleId: finding.ruleId,
|
|
172
|
+
reason: "Already excluded by .geminiignore",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
mergeSimpleIgnoreAction(actionsByKey, finding, pattern, evidencePath, {
|
|
177
|
+
agent: "gemini-cli",
|
|
178
|
+
targetRelativePath: ".geminiignore",
|
|
179
|
+
label: "Gemini CLI",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (wantsGemini && !hasGemini) {
|
|
184
|
+
skipped.push({
|
|
185
|
+
findingId: finding.id,
|
|
186
|
+
ruleId: finding.ruleId,
|
|
187
|
+
reason: "Gemini CLI not detected; skipping .geminiignore fix",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (wantsAider && hasAider) {
|
|
191
|
+
if (cursorIndex.matchesAiderignore(evidencePath) || cursorIndex.matchesAiderignore(pattern)) {
|
|
192
|
+
skipped.push({
|
|
193
|
+
findingId: finding.id,
|
|
194
|
+
ruleId: finding.ruleId,
|
|
195
|
+
reason: "Already excluded by .aiderignore",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
mergeSimpleIgnoreAction(actionsByKey, finding, pattern, evidencePath, {
|
|
200
|
+
agent: "aider",
|
|
201
|
+
targetRelativePath: ".aiderignore",
|
|
202
|
+
label: "Aider",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (wantsAider && !hasAider) {
|
|
207
|
+
skipped.push({
|
|
208
|
+
findingId: finding.id,
|
|
209
|
+
ruleId: finding.ruleId,
|
|
210
|
+
reason: "Aider not detected; skipping .aiderignore fix",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
151
213
|
for (const agent of finding.affectedAgents) {
|
|
152
|
-
if (agent === "cursor" ||
|
|
214
|
+
if (agent === "cursor" ||
|
|
215
|
+
agent === "claude-code" ||
|
|
216
|
+
agent === "codex" ||
|
|
217
|
+
agent === "gemini-cli" ||
|
|
218
|
+
agent === "aider") {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (agent === "copilot") {
|
|
222
|
+
skipped.push({
|
|
223
|
+
findingId: finding.id,
|
|
224
|
+
ruleId: finding.ruleId,
|
|
225
|
+
reason: "GitHub Copilot has no official project deny/ignore Fix writer; review instructions and .gitignore manually",
|
|
226
|
+
});
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (agent === "windsurf") {
|
|
230
|
+
skipped.push({
|
|
231
|
+
findingId: finding.id,
|
|
232
|
+
ruleId: finding.ruleId,
|
|
233
|
+
reason: "Windsurf has no official project deny/ignore Fix writer; review rules and .gitignore manually",
|
|
234
|
+
});
|
|
153
235
|
continue;
|
|
154
236
|
}
|
|
155
237
|
skipped.push({
|
|
@@ -225,8 +307,28 @@ function mergeCodexAction(actionsByKey, finding, pattern, evidencePath, denyKey)
|
|
|
225
307
|
description: `Exclude ${evidencePath} from Codex via filesystem deny "${denyKey}"`,
|
|
226
308
|
});
|
|
227
309
|
}
|
|
228
|
-
|
|
229
|
-
|
|
310
|
+
function mergeSimpleIgnoreAction(actionsByKey, finding, pattern, evidencePath, options) {
|
|
311
|
+
const key = `${options.agent}:${options.targetRelativePath}:${pattern}`;
|
|
312
|
+
const existing = actionsByKey.get(key);
|
|
313
|
+
if (existing) {
|
|
314
|
+
if (!existing.findingIds.includes(finding.id)) {
|
|
315
|
+
existing.findingIds.push(finding.id);
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
actionsByKey.set(key, {
|
|
320
|
+
id: key,
|
|
321
|
+
kind: "append-ignore-pattern",
|
|
322
|
+
agent: options.agent,
|
|
323
|
+
targetRelativePath: options.targetRelativePath,
|
|
324
|
+
pattern,
|
|
325
|
+
evidencePath,
|
|
326
|
+
findingIds: [finding.id],
|
|
327
|
+
description: `Exclude ${evidencePath} from ${options.label} via ${options.targetRelativePath}`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/** Patterns that would be newly appended given current ignore-file contents. */
|
|
331
|
+
export function missingPatternsForIgnoreFile(currentContent, patterns) {
|
|
230
332
|
const existing = currentContent === null ? [] : parseIgnoreFile(currentContent);
|
|
231
333
|
const missing = [];
|
|
232
334
|
for (const pattern of patterns) {
|
|
@@ -245,3 +347,5 @@ export function missingPatternsForCursorignore(currentContent, patterns) {
|
|
|
245
347
|
}
|
|
246
348
|
return missing;
|
|
247
349
|
}
|
|
350
|
+
/** @deprecated Prefer missingPatternsForIgnoreFile */
|
|
351
|
+
export const missingPatternsForCursorignore = missingPatternsForIgnoreFile;
|
|
@@ -4,5 +4,7 @@ export declare function renderFixPlanTerminal(plan: FixPlan, options: {
|
|
|
4
4
|
cursorContent: string | null;
|
|
5
5
|
claudeSettingsContent?: string | null;
|
|
6
6
|
codexConfigContent?: string | null;
|
|
7
|
+
geminiignoreContent?: string | null;
|
|
8
|
+
aiderignoreContent?: string | null;
|
|
7
9
|
applyResult?: FixApplyResult;
|
|
8
10
|
}): string;
|
package/dist/core/fix/render.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { previewClaudeSettingsActions } from "./writers/claude-settings.js";
|
|
2
2
|
import { previewCodexConfigActions } from "./writers/codex-config.js";
|
|
3
3
|
import { previewCursorignoreActions } from "./writers/cursorignore.js";
|
|
4
|
+
import { previewSimpleIgnoreActions } from "./writers/simple-ignore.js";
|
|
4
5
|
export function renderFixPlanTerminal(plan, options) {
|
|
5
6
|
const lines = [];
|
|
6
7
|
lines.push("");
|
|
@@ -39,41 +40,55 @@ export function renderFixPlanTerminal(plan, options) {
|
|
|
39
40
|
const cursorPreview = previewCursorignoreActions(options.cursorContent, plan.actions);
|
|
40
41
|
const claudePreview = previewClaudeSettingsActions(options.claudeSettingsContent ?? null, plan.actions);
|
|
41
42
|
const codexPreview = previewCodexConfigActions(options.codexConfigContent ?? null, plan.actions);
|
|
42
|
-
|
|
43
|
+
const geminiPreview = previewSimpleIgnoreActions(options.geminiignoreContent ?? null, plan.actions, { agent: "gemini-cli", targetRelativePath: ".geminiignore" });
|
|
44
|
+
const aiderPreview = previewSimpleIgnoreActions(options.aiderignoreContent ?? null, plan.actions, { agent: "aider", targetRelativePath: ".aiderignore" });
|
|
45
|
+
const previews = [
|
|
46
|
+
cursorPreview,
|
|
47
|
+
claudePreview,
|
|
48
|
+
codexPreview,
|
|
49
|
+
geminiPreview,
|
|
50
|
+
aiderPreview,
|
|
51
|
+
].filter((p) => p !== null);
|
|
52
|
+
if (previews.length > 0) {
|
|
43
53
|
lines.push("");
|
|
44
54
|
lines.push(" File changes:");
|
|
45
55
|
let shown = false;
|
|
46
|
-
|
|
47
|
-
lines.push(` ${cursorPreview.targetRelativePath} (+${cursorPreview.patternsToAdd.length} pattern(s))`);
|
|
48
|
-
lines.push("");
|
|
49
|
-
for (const previewLine of cursorPreview.preview.split("\n")) {
|
|
50
|
-
lines.push(` ${previewLine}`);
|
|
51
|
-
}
|
|
52
|
-
shown = true;
|
|
53
|
-
}
|
|
54
|
-
if (claudePreview) {
|
|
56
|
+
for (const preview of previews) {
|
|
55
57
|
if (shown) {
|
|
56
58
|
lines.push("");
|
|
57
59
|
}
|
|
58
|
-
|
|
60
|
+
const count = preview.patternsToAdd?.length ??
|
|
61
|
+
preview.denyRulesToAdd?.length ??
|
|
62
|
+
preview.denyKeysToAdd?.length ??
|
|
63
|
+
0;
|
|
64
|
+
const unit = preview.patternsToAdd
|
|
65
|
+
? "pattern(s)"
|
|
66
|
+
: preview.denyRulesToAdd
|
|
67
|
+
? "deny rule(s)"
|
|
68
|
+
: "deny key(s)";
|
|
69
|
+
lines.push(` ${preview.targetRelativePath} (+${count} ${unit})`);
|
|
59
70
|
lines.push("");
|
|
60
|
-
for (const previewLine of
|
|
71
|
+
for (const previewLine of preview.preview.split("\n")) {
|
|
61
72
|
lines.push(` ${previewLine}`);
|
|
62
73
|
}
|
|
63
74
|
shown = true;
|
|
64
75
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
lines.push(`
|
|
70
|
-
lines.push(
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
}
|
|
77
|
+
if (options.applyResult?.error) {
|
|
78
|
+
lines.push("");
|
|
79
|
+
if (options.applyResult.partial) {
|
|
80
|
+
lines.push(` Partial apply: wrote ${options.applyResult.writtenFiles.join(", ") || "(none)"} then failed on ${options.applyResult.failedTarget ?? "unknown target"}`);
|
|
81
|
+
lines.push(` Error: ${options.applyResult.error}`);
|
|
82
|
+
lines.push(" Safe Fix is not transactional across multiple files — review written paths manually.");
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
lines.push(` Fix aborted before/during write: ${options.applyResult.error}`);
|
|
86
|
+
if (options.applyResult.failedTarget) {
|
|
87
|
+
lines.push(` Target: ${options.applyResult.failedTarget}`);
|
|
73
88
|
}
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
|
-
if (options.applyResult && !options.dryRun) {
|
|
91
|
+
else if (options.applyResult && !options.dryRun) {
|
|
77
92
|
lines.push("");
|
|
78
93
|
if (options.applyResult.writtenFiles.length > 0) {
|
|
79
94
|
lines.push(` Wrote: ${options.applyResult.writtenFiles.join(", ")}`);
|
package/dist/core/fix/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import readline from "node:readline/promises";
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import { scan } from "../scanner/scan.js";
|
|
4
|
-
import { applyFixPlan, readClaudeSettings, readCodexConfig, readCursorignore } from "./apply.js";
|
|
4
|
+
import { applyFixPlan, readClaudeSettings, readCodexConfig, readCursorignore, readSimpleIgnore, } from "./apply.js";
|
|
5
5
|
import { buildFixPlan } from "./plan.js";
|
|
6
6
|
import { renderFixPlanTerminal } from "./render.js";
|
|
7
7
|
/**
|
|
@@ -13,6 +13,8 @@ export async function runFix(options) {
|
|
|
13
13
|
const cursorContent = await readCursorignore(plan.root);
|
|
14
14
|
const claudeSettingsContent = await readClaudeSettings(plan.root);
|
|
15
15
|
const codexConfigContent = await readCodexConfig(plan.root);
|
|
16
|
+
const geminiignoreContent = await readSimpleIgnore(plan.root, ".geminiignore");
|
|
17
|
+
const aiderignoreContent = await readSimpleIgnore(plan.root, ".aiderignore");
|
|
16
18
|
if (plan.actions.length === 0 || options.dryRun) {
|
|
17
19
|
const applyResult = await applyFixPlan(plan, { dryRun: true });
|
|
18
20
|
return { plan, applyResult, cancelled: false };
|
|
@@ -23,6 +25,8 @@ export async function runFix(options) {
|
|
|
23
25
|
cursorContent,
|
|
24
26
|
claudeSettingsContent,
|
|
25
27
|
codexConfigContent,
|
|
28
|
+
geminiignoreContent,
|
|
29
|
+
aiderignoreContent,
|
|
26
30
|
});
|
|
27
31
|
process.stdout.write(previewText);
|
|
28
32
|
const confirmed = await confirm("Apply these changes?");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FixAllowlistedPath } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve an allowlisted Fix target and refuse symlink write-through /
|
|
4
|
+
* path-escape before any Safe Fix writer mutates the filesystem.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveSafeFixWritePath(root: string, relativePath: string): Promise<{
|
|
7
|
+
absolutePath: string;
|
|
8
|
+
relativePath: FixAllowlistedPath;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* Preflight every planned Fix target before any writer mutates the repo.
|
|
12
|
+
* Fails fast if any target is unsafe; does not create files.
|
|
13
|
+
*/
|
|
14
|
+
export declare function preflightSafeFixTargets(root: string, relativePaths: readonly string[]): Promise<FixAllowlistedPath[]>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FIX_PATH_ALLOWLIST } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve an allowlisted Fix target and refuse symlink write-through /
|
|
6
|
+
* path-escape before any Safe Fix writer mutates the filesystem.
|
|
7
|
+
*/
|
|
8
|
+
export async function resolveSafeFixWritePath(root, relativePath) {
|
|
9
|
+
if (!FIX_PATH_ALLOWLIST.includes(relativePath)) {
|
|
10
|
+
throw new Error(`Fix target is not allowlisted: ${relativePath}`);
|
|
11
|
+
}
|
|
12
|
+
const allowlisted = relativePath;
|
|
13
|
+
if (path.isAbsolute(allowlisted) || allowlisted.split(/[/\\]/).includes("..")) {
|
|
14
|
+
throw new Error(`Fix target path is invalid: ${allowlisted}`);
|
|
15
|
+
}
|
|
16
|
+
const rootReal = await fs.realpath(root);
|
|
17
|
+
const absolutePath = path.resolve(rootReal, allowlisted);
|
|
18
|
+
const relativeToRoot = path.relative(rootReal, absolutePath);
|
|
19
|
+
if (relativeToRoot.startsWith("..") ||
|
|
20
|
+
path.isAbsolute(relativeToRoot) ||
|
|
21
|
+
relativeToRoot.includes(`..${path.sep}`)) {
|
|
22
|
+
throw new Error(`Fix target escapes repository root: ${allowlisted}`);
|
|
23
|
+
}
|
|
24
|
+
await assertNoSymlinkAncestors(rootReal, absolutePath, allowlisted);
|
|
25
|
+
try {
|
|
26
|
+
const stat = await fs.lstat(absolutePath);
|
|
27
|
+
if (stat.isSymbolicLink()) {
|
|
28
|
+
throw new Error(`refusing to write through symlink Fix target: ${allowlisted} (replace the symlink with a regular file first)`);
|
|
29
|
+
}
|
|
30
|
+
if (stat.isDirectory()) {
|
|
31
|
+
throw new Error(`refusing Fix target that is a directory: ${allowlisted} (expected a regular file)`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const code = error && typeof error === "object" && "code" in error
|
|
36
|
+
? String(error.code)
|
|
37
|
+
: "";
|
|
38
|
+
if (code !== "ENOENT") {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { absolutePath, relativePath: allowlisted };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Preflight every planned Fix target before any writer mutates the repo.
|
|
46
|
+
* Fails fast if any target is unsafe; does not create files.
|
|
47
|
+
*/
|
|
48
|
+
export async function preflightSafeFixTargets(root, relativePaths) {
|
|
49
|
+
const unique = [...new Set(relativePaths)];
|
|
50
|
+
const resolved = [];
|
|
51
|
+
for (const relativePath of unique) {
|
|
52
|
+
const { relativePath: allowlisted } = await resolveSafeFixWritePath(root, relativePath);
|
|
53
|
+
resolved.push(allowlisted);
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
57
|
+
async function assertNoSymlinkAncestors(rootReal, absolutePath, label) {
|
|
58
|
+
let current = path.dirname(absolutePath);
|
|
59
|
+
while (current.startsWith(rootReal) && current !== rootReal) {
|
|
60
|
+
try {
|
|
61
|
+
const stat = await fs.lstat(current);
|
|
62
|
+
if (stat.isSymbolicLink()) {
|
|
63
|
+
throw new Error(`refusing Fix target under symlink directory: ${label} (ancestor ${path.relative(rootReal, current)} is a symlink)`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const code = error && typeof error === "object" && "code" in error
|
|
68
|
+
? String(error.code)
|
|
69
|
+
: "";
|
|
70
|
+
if (code === "ENOENT") {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
const parent = path.dirname(current);
|
|
76
|
+
if (parent === current) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
current = parent;
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/core/fix/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentId } from "../../types/index.js";
|
|
2
2
|
export type FixKind = "append-ignore-pattern";
|
|
3
3
|
/** Allowlisted relative paths fix may create or modify. */
|
|
4
|
-
export declare const FIX_PATH_ALLOWLIST: readonly [".cursorignore", ".claude/settings.json", ".codex/config.toml"];
|
|
4
|
+
export declare const FIX_PATH_ALLOWLIST: readonly [".cursorignore", ".claude/settings.json", ".codex/config.toml", ".geminiignore", ".aiderignore"];
|
|
5
5
|
export type FixAllowlistedPath = (typeof FIX_PATH_ALLOWLIST)[number];
|
|
6
6
|
export interface FixAction {
|
|
7
7
|
id: string;
|
|
@@ -31,4 +31,15 @@ export interface FixApplyResult {
|
|
|
31
31
|
writtenFiles: string[];
|
|
32
32
|
/** Relative paths that would be / were written */
|
|
33
33
|
changedFiles: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Present when apply stopped after one or more successful writes.
|
|
36
|
+
* Safe Fix is not transactional across multiple targets.
|
|
37
|
+
*/
|
|
38
|
+
partial?: boolean;
|
|
39
|
+
/** Relative path that failed when `partial` is true, or preflight failed. */
|
|
40
|
+
failedTarget?: string;
|
|
41
|
+
/** Human-readable failure reason. */
|
|
42
|
+
error?: string;
|
|
43
|
+
/** Audit / backup id created before apply (when writes were attempted). */
|
|
44
|
+
auditId?: string;
|
|
34
45
|
}
|
package/dist/core/fix/types.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { atomicWriteTextFile, readTextFile } from "../../../utils/fs.js";
|
|
3
3
|
import { claudeReadDenyRule } from "../../rules/claude-deny.js";
|
|
4
|
+
import { resolveSafeFixWritePath } from "../safe-target.js";
|
|
4
5
|
const MAX_BYTES = 512 * 1024;
|
|
5
6
|
const SETTINGS_RELATIVE = ".claude/settings.json";
|
|
6
7
|
export function denyRuleForFixPattern(pattern, evidencePath) {
|
|
@@ -70,7 +71,8 @@ export async function readClaudeSettings(root) {
|
|
|
70
71
|
return readTextFile(path.join(root, SETTINGS_RELATIVE), MAX_BYTES);
|
|
71
72
|
}
|
|
72
73
|
export async function writeClaudeSettings(root, content) {
|
|
73
|
-
await
|
|
74
|
+
const { absolutePath } = await resolveSafeFixWritePath(root, SETTINGS_RELATIVE);
|
|
75
|
+
await atomicWriteTextFile(absolutePath, content);
|
|
74
76
|
}
|
|
75
77
|
function parseDenyList(currentContent) {
|
|
76
78
|
if (!currentContent || currentContent.trim().length === 0) {
|
|
@@ -19,4 +19,7 @@ export declare function previewCodexConfigActions(currentContent: string | null,
|
|
|
19
19
|
export declare function readCodexConfig(root: string): Promise<string | null>;
|
|
20
20
|
export declare function writeCodexConfig(root: string, content: string): Promise<void>;
|
|
21
21
|
export declare function assertWritableCodexConfig(content: string): void;
|
|
22
|
+
/** Escape a filesystem deny key for a TOML basic string assignment. */
|
|
23
|
+
export declare function formatTomlDenyAssignment(key: string): string;
|
|
24
|
+
export declare function assertSafeCodexDenyKey(key: string): void;
|
|
22
25
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { atomicWriteTextFile, readTextFile } from "../../../utils/fs.js";
|
|
3
3
|
import { codexDenyKey, configTextDeniesPath } from "../../rules/codex-deny.js";
|
|
4
|
+
import { resolveSafeFixWritePath } from "../safe-target.js";
|
|
4
5
|
const MAX_BYTES = 512 * 1024;
|
|
5
6
|
const CONFIG_RELATIVE = ".codex/config.toml";
|
|
6
7
|
const AGENTDOCTOR_PROFILE = "agentdoctor_context";
|
|
@@ -55,7 +56,7 @@ export function buildCodexConfigContent(currentContent, denyKeysToAdd) {
|
|
|
55
56
|
next += `"." = "write"\n`;
|
|
56
57
|
}
|
|
57
58
|
for (const key of denyKeysToAdd) {
|
|
58
|
-
next +=
|
|
59
|
+
next += `${formatTomlDenyAssignment(key)}\n`;
|
|
59
60
|
}
|
|
60
61
|
if (!next.includes(END_MARKER)) {
|
|
61
62
|
next += `${END_MARKER}\n`;
|
|
@@ -68,7 +69,7 @@ export function buildCodexConfigContent(currentContent, denyKeysToAdd) {
|
|
|
68
69
|
if (configTextDeniesPath(body, key) || configTextDeniesPath(next, key)) {
|
|
69
70
|
continue;
|
|
70
71
|
}
|
|
71
|
-
linesToInsert.push(
|
|
72
|
+
linesToInsert.push(formatTomlDenyAssignment(key).trimEnd());
|
|
72
73
|
}
|
|
73
74
|
if (linesToInsert.length === 0) {
|
|
74
75
|
return next;
|
|
@@ -105,7 +106,8 @@ export async function readCodexConfig(root) {
|
|
|
105
106
|
return readTextFile(path.join(root, CONFIG_RELATIVE), MAX_BYTES);
|
|
106
107
|
}
|
|
107
108
|
export async function writeCodexConfig(root, content) {
|
|
108
|
-
await
|
|
109
|
+
const { absolutePath } = await resolveSafeFixWritePath(root, CONFIG_RELATIVE);
|
|
110
|
+
await atomicWriteTextFile(absolutePath, content);
|
|
109
111
|
}
|
|
110
112
|
export function assertWritableCodexConfig(content) {
|
|
111
113
|
if (/^\s*sandbox_mode\s*=/m.test(content)) {
|
|
@@ -171,6 +173,25 @@ function ensureTrailingNewline(value) {
|
|
|
171
173
|
}
|
|
172
174
|
return `${value}\n`;
|
|
173
175
|
}
|
|
176
|
+
/** Escape a filesystem deny key for a TOML basic string assignment. */
|
|
177
|
+
export function formatTomlDenyAssignment(key) {
|
|
178
|
+
assertSafeCodexDenyKey(key);
|
|
179
|
+
return `"${escapeTomlBasicString(key)}" = "deny"`;
|
|
180
|
+
}
|
|
181
|
+
export function assertSafeCodexDenyKey(key) {
|
|
182
|
+
if (key.length === 0) {
|
|
183
|
+
throw new Error("refusing empty Codex deny key");
|
|
184
|
+
}
|
|
185
|
+
for (let i = 0; i < key.length; i++) {
|
|
186
|
+
const code = key.charCodeAt(i);
|
|
187
|
+
if (code < 32) {
|
|
188
|
+
throw new Error("refusing Codex deny key with control characters");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function escapeTomlBasicString(value) {
|
|
193
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
194
|
+
}
|
|
174
195
|
function formatSimpleDiff(fileLabel, before, after) {
|
|
175
196
|
const lines = [];
|
|
176
197
|
lines.push(`--- a/${fileLabel}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { atomicWriteTextFile, readTextFile } from "../../../utils/fs.js";
|
|
3
|
+
import { resolveSafeFixWritePath } from "../safe-target.js";
|
|
3
4
|
import { missingPatternsForCursorignore } from "../plan.js";
|
|
4
5
|
const MAX_BYTES = 512 * 1024;
|
|
5
6
|
const BANNER = "# Added by AgentDoctor";
|
|
@@ -40,7 +41,8 @@ export async function readCursorignore(root) {
|
|
|
40
41
|
return readTextFile(path.join(root, ".cursorignore"), MAX_BYTES);
|
|
41
42
|
}
|
|
42
43
|
export async function writeCursorignore(root, content) {
|
|
43
|
-
await
|
|
44
|
+
const { absolutePath } = await resolveSafeFixWritePath(root, ".cursorignore");
|
|
45
|
+
await atomicWriteTextFile(absolutePath, content);
|
|
44
46
|
}
|
|
45
47
|
function formatSimpleDiff(fileLabel, before, after) {
|
|
46
48
|
const beforeLines = before.length === 0 ? [] : before.split(/\r?\n/);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AgentId } from "../../../types/index.js";
|
|
2
|
+
import type { FixAction, FixAllowlistedPath } from "../types.js";
|
|
3
|
+
export interface SimpleIgnoreWritePreview {
|
|
4
|
+
targetRelativePath: FixAllowlistedPath;
|
|
5
|
+
patternsToAdd: string[];
|
|
6
|
+
before: string;
|
|
7
|
+
after: string;
|
|
8
|
+
preview: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function buildSimpleIgnoreContent(currentContent: string | null, patternsToAdd: string[]): string;
|
|
11
|
+
export declare function previewSimpleIgnoreActions(currentContent: string | null, actions: FixAction[], options: {
|
|
12
|
+
agent: AgentId;
|
|
13
|
+
targetRelativePath: FixAllowlistedPath;
|
|
14
|
+
}): SimpleIgnoreWritePreview | null;
|
|
15
|
+
export declare function readSimpleIgnore(root: string, relativePath: string): Promise<string | null>;
|
|
16
|
+
export declare function writeSimpleIgnore(root: string, relativePath: string, content: string): Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { atomicWriteTextFile, readTextFile } from "../../../utils/fs.js";
|
|
3
|
+
import { missingPatternsForIgnoreFile } from "../plan.js";
|
|
4
|
+
import { resolveSafeFixWritePath } from "../safe-target.js";
|
|
5
|
+
const MAX_BYTES = 512 * 1024;
|
|
6
|
+
const BANNER = "# Added by AgentDoctor";
|
|
7
|
+
export function buildSimpleIgnoreContent(currentContent, patternsToAdd) {
|
|
8
|
+
if (patternsToAdd.length === 0) {
|
|
9
|
+
return currentContent ?? "";
|
|
10
|
+
}
|
|
11
|
+
const base = currentContent ?? "";
|
|
12
|
+
const trimmed = base.replace(/\s+$/, "");
|
|
13
|
+
const block = [BANNER, ...patternsToAdd].join("\n");
|
|
14
|
+
if (trimmed.length === 0) {
|
|
15
|
+
return `${block}\n`;
|
|
16
|
+
}
|
|
17
|
+
return `${trimmed}\n\n${block}\n`;
|
|
18
|
+
}
|
|
19
|
+
export function previewSimpleIgnoreActions(currentContent, actions, options) {
|
|
20
|
+
const matched = actions.filter((a) => a.agent === options.agent && a.targetRelativePath === options.targetRelativePath);
|
|
21
|
+
if (matched.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const requested = [...new Set(matched.map((a) => a.pattern))];
|
|
25
|
+
const patternsToAdd = missingPatternsForIgnoreFile(currentContent, requested);
|
|
26
|
+
if (patternsToAdd.length === 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const before = currentContent ?? "";
|
|
30
|
+
const after = buildSimpleIgnoreContent(currentContent, patternsToAdd);
|
|
31
|
+
const preview = formatSimpleDiff(options.targetRelativePath, before, after);
|
|
32
|
+
return {
|
|
33
|
+
targetRelativePath: options.targetRelativePath,
|
|
34
|
+
patternsToAdd,
|
|
35
|
+
before,
|
|
36
|
+
after,
|
|
37
|
+
preview,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export async function readSimpleIgnore(root, relativePath) {
|
|
41
|
+
return readTextFile(path.join(root, relativePath), MAX_BYTES);
|
|
42
|
+
}
|
|
43
|
+
export async function writeSimpleIgnore(root, relativePath, content) {
|
|
44
|
+
const { absolutePath } = await resolveSafeFixWritePath(root, relativePath);
|
|
45
|
+
await atomicWriteTextFile(absolutePath, content);
|
|
46
|
+
}
|
|
47
|
+
function formatSimpleDiff(fileLabel, before, after) {
|
|
48
|
+
const beforeLines = before.length === 0 ? [] : before.split(/\r?\n/);
|
|
49
|
+
const afterLines = after.split(/\r?\n/);
|
|
50
|
+
const lines = [];
|
|
51
|
+
lines.push(`--- a/${fileLabel}`);
|
|
52
|
+
lines.push(`+++ b/${fileLabel}`);
|
|
53
|
+
const addedStart = beforeLines.length === 0 ? 0 : beforeLines.length;
|
|
54
|
+
for (let i = addedStart; i < afterLines.length; i++) {
|
|
55
|
+
const line = afterLines[i];
|
|
56
|
+
if (line === undefined) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
lines.push(`+${line}`);
|
|
60
|
+
}
|
|
61
|
+
if (before.length === 0) {
|
|
62
|
+
lines.splice(2, 0, "@@ new file @@");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
lines.splice(2, 0, "@@ append @@");
|
|
66
|
+
}
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface WorkspacePackage {
|
|
2
|
+
name: string;
|
|
3
|
+
relativePath: string;
|
|
4
|
+
absolutePath: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MonorepoDetection {
|
|
7
|
+
root: string;
|
|
8
|
+
isMonorepo: boolean;
|
|
9
|
+
tool: "npm-workspaces" | "pnpm-workspaces" | "none";
|
|
10
|
+
packages: WorkspacePackage[];
|
|
11
|
+
limitations: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function detectMonorepo(rootInput: string): Promise<MonorepoDetection>;
|