claude-setup 1.1.1 → 1.1.3

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.
@@ -4,39 +4,70 @@ import { collectProjectFiles, isEmptyProject } from "../collect.js";
4
4
  import { readState } from "../state.js";
5
5
  import { updateManifest } from "../manifest.js";
6
6
  import { buildEmptyProjectCommand, buildAtomicSteps, buildOrchestratorCommand, } from "../builder.js";
7
+ import { c } from "../output.js";
8
+ import { ensureConfig } from "../config.js";
7
9
  function ensureDir(dir) {
8
10
  if (!existsSync(dir))
9
11
  mkdirSync(dir, { recursive: true });
10
12
  }
11
- export async function runInit() {
13
+ export async function runInit(opts = {}) {
14
+ const dryRun = opts.dryRun ?? false;
15
+ // Auto-generate .claude-setup.json if it doesn't exist
16
+ // Developer can edit it anytime to tune token budgets, truncation rules, etc.
17
+ const configCreated = ensureConfig();
18
+ if (configCreated) {
19
+ console.log(`${c.dim("Created .claude-setup.json — edit to tune token budgets and truncation rules")}`);
20
+ }
12
21
  const state = await readState();
13
22
  const collected = await collectProjectFiles(process.cwd(), "deep");
14
- ensureDir(".claude/commands");
15
23
  if (isEmptyProject(collected)) {
16
24
  const content = buildEmptyProjectCommand();
25
+ if (dryRun) {
26
+ console.log(c.bold("[DRY RUN] Would write:\n"));
27
+ console.log(` .claude/commands/stack-init.md (${content.length} chars, ~${Math.ceil(content.length / 4)} tokens)`);
28
+ console.log(`\n${c.dim("--- preview ---")}`);
29
+ console.log(content.slice(0, 500));
30
+ if (content.length > 500)
31
+ console.log(c.dim(`\n... +${content.length - 500} chars`));
32
+ return;
33
+ }
34
+ ensureDir(".claude/commands");
17
35
  writeFileSync(".claude/commands/stack-init.md", content, "utf8");
18
36
  await updateManifest("init", collected);
19
37
  console.log(`
20
- ✅ New project detected.
38
+ ${c.green("")} New project detected.
21
39
 
22
40
  Open Claude Code and run:
23
- /stack-init
41
+ ${c.cyan("/stack-init")}
24
42
 
25
43
  Claude Code will ask 3 questions, then set up your environment.
26
44
  `);
27
45
  return;
28
46
  }
29
- // Standard init — 6 atomic steps + orchestrator
47
+ // Standard init — atomic steps + orchestrator
30
48
  const steps = buildAtomicSteps(collected, state);
49
+ const orchestrator = buildOrchestratorCommand(steps);
50
+ if (dryRun) {
51
+ console.log(c.bold("[DRY RUN] Would write:\n"));
52
+ for (const step of steps) {
53
+ const tokens = Math.ceil(step.content.length / 4);
54
+ console.log(` .claude/commands/${step.filename} (${step.content.length} chars, ~${tokens} tokens)`);
55
+ }
56
+ console.log(` .claude/commands/stack-init.md (orchestrator)`);
57
+ const totalTokens = steps.reduce((sum, s) => sum + Math.ceil(s.content.length / 4), 0);
58
+ console.log(`\n${c.dim(`Total: ~${totalTokens} tokens across ${steps.length} files`)}`);
59
+ return;
60
+ }
61
+ ensureDir(".claude/commands");
31
62
  for (const step of steps) {
32
63
  writeFileSync(join(".claude/commands", step.filename), step.content, "utf8");
33
64
  }
34
- writeFileSync(".claude/commands/stack-init.md", buildOrchestratorCommand(steps), "utf8");
65
+ writeFileSync(".claude/commands/stack-init.md", orchestrator, "utf8");
35
66
  await updateManifest("init", collected);
36
67
  console.log(`
37
- ✅ Ready. Open Claude Code and run:
38
- /stack-init
68
+ ${c.green("")} Ready. Open Claude Code and run:
69
+ ${c.cyan("/stack-init")}
39
70
 
40
- Runs 6 atomic steps. If one fails, re-run only that step.
71
+ Runs ${steps.length - 1} atomic steps. If one fails, re-run only that step.
41
72
  `);
42
73
  }
@@ -4,6 +4,7 @@ import { collectProjectFiles } from "../collect.js";
4
4
  import { readState } from "../state.js";
5
5
  import { updateManifest } from "../manifest.js";
6
6
  import { buildRemoveCommand } from "../builder.js";
7
+ import { c } from "../output.js";
7
8
  function ensureDir(dir) {
8
9
  if (!existsSync(dir))
9
10
  mkdirSync(dir, { recursive: true });
@@ -29,5 +30,5 @@ export async function runRemove() {
29
30
  ensureDir(".claude/commands");
30
31
  writeFileSync(".claude/commands/stack-remove.md", content, "utf8");
31
32
  await updateManifest("remove", collected, { input: userInput });
32
- console.log(`\n✅ Ready. Open Claude Code and run:\n /stack-remove\n`);
33
+ console.log(`\n${c.green("")} Ready. Open Claude Code and run:\n ${c.cyan("/stack-remove")}\n`);
33
34
  }
@@ -1,23 +1,135 @@
1
1
  import { readManifest } from "../manifest.js";
2
2
  import { readState } from "../state.js";
3
+ import { detectOS } from "../os.js";
4
+ import { c, statusLine, section } from "../output.js";
5
+ function safeJsonParse(content) {
6
+ try {
7
+ return JSON.parse(content);
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
3
13
  export async function runStatus() {
4
14
  const manifest = await readManifest();
5
15
  const state = await readState();
16
+ const os = detectOS();
6
17
  if (!manifest) {
7
- console.log("No setup found.\n Run: npx claude-setup init");
18
+ console.log(`${c.yellow("⚠️ No setup found.")}\n Run: ${c.cyan("npx claude-setup init")}`);
8
19
  return;
9
20
  }
10
21
  const last = manifest.runs.at(-1);
11
- console.log(`Last: ${last.command} at ${last.at} (v${last.claudeStackVersion})\n`);
12
- console.log(`CLAUDE.md ${state.claudeMd.exists ? "✅" : "❌ missing"}`);
13
- console.log(`.mcp.json ${state.mcpJson.exists ? "" : "❌ missing"}`);
14
- console.log(`settings.json ${state.settings.exists ? "✅" : "❌ missing"}`);
15
- console.log(`Skills ${state.skills.length || "none"}`);
16
- console.log(`Workflows ${state.workflows.length || "none"}`);
17
- console.log(`\nHistory (last 5):`);
22
+ const version = last.claudeStackVersion ?? "unknown";
23
+ // --- Header ---
24
+ console.log(c.bold("status") + ` — ${new Date().toISOString().split("T")[0]}\n`);
25
+ // --- Project info ---
26
+ const projectType = inferProjectType(state);
27
+ console.log(`Project : ${projectType}`);
28
+ console.log(`OS : ${os}`);
29
+ console.log(`Version : claude-setup v${version}`);
30
+ // --- Setup files ---
31
+ section("Setup files");
32
+ // CLAUDE.md
33
+ if (state.claudeMd.exists) {
34
+ statusLine("✅", "CLAUDE.md", "exists");
35
+ }
36
+ else {
37
+ statusLine("❌", "CLAUDE.md", "missing");
38
+ }
39
+ // .mcp.json
40
+ if (state.mcpJson.exists && state.mcpJson.content) {
41
+ const mcp = safeJsonParse(state.mcpJson.content);
42
+ const serverCount = mcp?.mcpServers
43
+ ? Object.keys(mcp.mcpServers).length
44
+ : 0;
45
+ statusLine("✅", ".mcp.json", `${serverCount} server(s)`);
46
+ }
47
+ else {
48
+ statusLine("❌", ".mcp.json", "missing");
49
+ }
50
+ // settings.json
51
+ if (state.settings.exists && state.settings.content) {
52
+ const settings = safeJsonParse(state.settings.content);
53
+ let hookCount = 0;
54
+ if (settings) {
55
+ for (const key of ["PreToolUse", "PostToolUse", "PostToolUseFailure", "Stop", "SessionStart"]) {
56
+ const hooks = settings[key];
57
+ if (Array.isArray(hooks))
58
+ hookCount += hooks.length;
59
+ }
60
+ }
61
+ statusLine("✅", "settings.json", `${hookCount} hook(s)`);
62
+ }
63
+ else {
64
+ statusLine("❌", "settings.json", "missing");
65
+ }
66
+ // Lists
67
+ console.log(` Skills : ${state.skills.length ? state.skills.map(s => s.split("/").at(-2) ?? s).join(", ") : "none"}`);
68
+ console.log(` Commands : ${state.commands.length ? state.commands.map(s => s.split("/").pop()?.replace(".md", "") ?? s).join(", ") : "none"}`);
69
+ console.log(` Workflows : ${state.workflows.length ? state.workflows.map(s => s.split("/").pop() ?? s).join(", ") : "none"}`);
70
+ // --- Run history ---
71
+ section("Run history (last 5)");
18
72
  for (const r of manifest.runs.slice(-5)) {
19
- console.log(` ${r.at} ${r.command}${r.input ? ` — "${r.input}"` : ""}`);
73
+ const inputStr = r.input ? ` — "${r.input}"` : "";
74
+ console.log(` ${c.dim(r.at)} ${r.command}${inputStr}`);
75
+ }
76
+ // --- Health hint ---
77
+ const hint = getHealthHint(manifest, state);
78
+ if (hint) {
79
+ section("Health hint");
80
+ console.log(` ${hint}`);
20
81
  }
21
- console.log("\n npx claude-setup sync — update after changes");
22
- console.log(" npx claude-setup doctor — validate environment");
82
+ // --- Next action ---
83
+ const next = getNextAction(manifest, state);
84
+ if (next) {
85
+ section("Next action");
86
+ console.log(` ${next}`);
87
+ }
88
+ console.log("");
89
+ }
90
+ function inferProjectType(state) {
91
+ if (state.claudeMd.content) {
92
+ // Try to infer from CLAUDE.md content
93
+ const content = state.claudeMd.content.toLowerCase();
94
+ if (content.includes("typescript") || content.includes("node"))
95
+ return "Node.js / TypeScript";
96
+ if (content.includes("python"))
97
+ return "Python";
98
+ if (content.includes("go ") || content.includes("golang"))
99
+ return "Go";
100
+ if (content.includes("rust"))
101
+ return "Rust";
102
+ if (content.includes("ruby"))
103
+ return "Ruby";
104
+ if (content.includes("java") && !content.includes("javascript"))
105
+ return "Java";
106
+ }
107
+ return c.dim("unknown — run init to detect");
108
+ }
109
+ function getHealthHint(manifest, _state) {
110
+ const last = manifest.runs.at(-1);
111
+ if (!last)
112
+ return `${c.yellow("⚠️")} No runs recorded. Run: ${c.cyan("npx claude-setup init")}`;
113
+ const daysSince = Math.floor((Date.now() - new Date(last.at).getTime()) / (1000 * 60 * 60 * 24));
114
+ // Check for recent deletions
115
+ if (last.command === "sync") {
116
+ const snapshot = last.snapshot;
117
+ const deletionCount = Object.keys(snapshot).filter(k => k.startsWith("[deleted]")).length;
118
+ if (deletionCount > 0) {
119
+ return `${c.red("🔴")} Last sync detected ${deletionCount} deletion(s). Verify setup is still valid.`;
120
+ }
121
+ }
122
+ if (daysSince > 7) {
123
+ return `${c.yellow("⚠️")} ${daysSince} day(s) since last sync. Source files may have drifted.`;
124
+ }
125
+ return `${c.green("✅")} Setup looks current.`;
126
+ }
127
+ function getNextAction(manifest, _state) {
128
+ const last = manifest.runs.at(-1);
129
+ if (!last)
130
+ return `Run ${c.cyan("npx claude-setup init")} to set up your project.`;
131
+ const daysSince = Math.floor((Date.now() - new Date(last.at).getTime()) / (1000 * 60 * 60 * 24));
132
+ if (daysSince > 7)
133
+ return `Run ${c.cyan("npx claude-setup sync")} to check for changes.`;
134
+ return null;
23
135
  }
@@ -1 +1,3 @@
1
- export declare function runSync(): Promise<void>;
1
+ export declare function runSync(opts?: {
2
+ dryRun?: boolean;
3
+ }): Promise<void>;
@@ -1,8 +1,10 @@
1
- import { writeFileSync, mkdirSync, existsSync } from "fs";
1
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
2
+ import { join } from "path";
2
3
  import { collectProjectFiles } from "../collect.js";
3
4
  import { readState } from "../state.js";
4
5
  import { readManifest, sha256, updateManifest } from "../manifest.js";
5
6
  import { buildSyncCommand } from "../builder.js";
7
+ import { c } from "../output.js";
6
8
  function ensureDir(dir) {
7
9
  if (!existsSync(dir))
8
10
  mkdirSync(dir, { recursive: true });
@@ -21,6 +23,9 @@ function computeDiff(snapshot, collected) {
21
23
  const changed = [];
22
24
  const deleted = [];
23
25
  for (const [path, content] of Object.entries(current)) {
26
+ // Skip virtual keys — they're not real files
27
+ if (path === "__digest__")
28
+ continue;
24
29
  const hash = sha256(content);
25
30
  if (!snapshot[path]) {
26
31
  added.push({ path, content: truncate(content, 2000) });
@@ -30,34 +35,84 @@ function computeDiff(snapshot, collected) {
30
35
  }
31
36
  }
32
37
  for (const path of Object.keys(snapshot)) {
38
+ // Skip virtual keys
39
+ if (path === "__digest__")
40
+ continue;
33
41
  if (!current[path])
34
42
  deleted.push(path);
35
43
  }
36
44
  return { added, changed, deleted };
37
45
  }
38
- export async function runSync() {
46
+ export async function runSync(opts = {}) {
47
+ const dryRun = opts.dryRun ?? false;
39
48
  const manifest = await readManifest();
40
49
  if (!manifest?.runs.length) {
41
- console.log("No previous run found. Start with: npx claude-setup init");
50
+ console.log(`No previous run found. Start with: ${c.cyan("npx claude-setup init")}`);
42
51
  return;
43
52
  }
44
53
  const lastRun = manifest.runs.at(-1);
45
- const collected = await collectProjectFiles(process.cwd(), "normal");
54
+ const cwd = process.cwd();
55
+ // --- Out-of-band edit detection ---
56
+ // Check if CLI-managed files were modified outside the CLI (e.g. by Claude Code directly)
57
+ const managedFiles = [
58
+ { label: "CLAUDE.md", path: join(cwd, "CLAUDE.md"), snapshotKey: "CLAUDE.md" },
59
+ { label: ".mcp.json", path: join(cwd, ".mcp.json"), snapshotKey: ".mcp.json" },
60
+ { label: "settings.json", path: join(cwd, ".claude", "settings.json"), snapshotKey: ".claude/settings.json" },
61
+ ];
62
+ let oobDetected = false;
63
+ for (const mf of managedFiles) {
64
+ if (!existsSync(mf.path))
65
+ continue;
66
+ const currentContent = readFileSync(mf.path, "utf8");
67
+ const currentHash = sha256(currentContent);
68
+ const snapshotHash = lastRun.snapshot[mf.snapshotKey];
69
+ if (snapshotHash && currentHash !== snapshotHash) {
70
+ if (!oobDetected) {
71
+ oobDetected = true;
72
+ console.log("");
73
+ }
74
+ console.log(`${c.yellow("⚠️")} OUT-OF-BAND EDIT — ${mf.label} was modified outside the CLI`);
75
+ console.log(` Re-snapshotting. Run ${c.cyan("npx claude-setup doctor")} to validate the new state.`);
76
+ }
77
+ }
78
+ if (oobDetected)
79
+ console.log("");
80
+ const collected = await collectProjectFiles(cwd, "normal");
46
81
  const diff = computeDiff(lastRun.snapshot, collected);
47
- if (!diff.added.length && !diff.changed.length && !diff.deleted.length) {
48
- console.log(`✅ No changes since ${lastRun.at}. Setup is current.`);
82
+ if (!diff.added.length && !diff.changed.length && !diff.deleted.length && !oobDetected) {
83
+ console.log(`${c.green("✅")} No changes since ${c.dim(lastRun.at)}. Setup is current.`);
49
84
  return;
50
85
  }
51
86
  const state = await readState();
52
87
  const content = buildSyncCommand(diff, collected, state);
88
+ if (dryRun) {
89
+ console.log(c.bold("[DRY RUN] Changes detected:\n"));
90
+ if (diff.added.length) {
91
+ console.log(c.green(` +${diff.added.length} added`));
92
+ for (const f of diff.added)
93
+ console.log(` ${f.path}`);
94
+ }
95
+ if (diff.changed.length) {
96
+ console.log(c.yellow(` ~${diff.changed.length} modified`));
97
+ for (const f of diff.changed)
98
+ console.log(` ${f.path}`);
99
+ }
100
+ if (diff.deleted.length) {
101
+ console.log(c.red(` -${diff.deleted.length} deleted`));
102
+ for (const f of diff.deleted)
103
+ console.log(` ${f}`);
104
+ }
105
+ console.log(`\n Would write: .claude/commands/stack-sync.md (~${Math.ceil(content.length / 4)} tokens)`);
106
+ return;
107
+ }
53
108
  ensureDir(".claude/commands");
54
109
  writeFileSync(".claude/commands/stack-sync.md", content, "utf8");
55
110
  await updateManifest("sync", collected);
56
111
  console.log(`
57
- Changes since ${lastRun.at}:
58
- +${diff.added.length} added ~${diff.changed.length} modified -${diff.deleted.length} deleted
112
+ Changes since ${c.dim(lastRun.at)}:
113
+ ${c.green(`+${diff.added.length}`)} added ${c.yellow(`~${diff.changed.length}`)} modified ${c.red(`-${diff.deleted.length}`)} deleted
59
114
 
60
- ✅ Ready. Open Claude Code and run:
61
- /stack-sync
115
+ ${c.green("")} Ready. Open Claude Code and run:
116
+ ${c.cyan("/stack-sync")}
62
117
  `);
63
118
  }
package/dist/config.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ export interface TruncationRule {
2
+ maxLines?: number;
3
+ metadataOnly?: boolean;
4
+ maxBytes?: number;
5
+ }
1
6
  export interface SetupConfig {
2
7
  maxSourceFiles: number;
3
8
  maxDepth: number;
@@ -11,5 +16,14 @@ export interface SetupConfig {
11
16
  digestMode: boolean;
12
17
  extraBlockedDirs: string[];
13
18
  sourceDirs: string[];
19
+ truncationRules: Record<string, TruncationRule>;
14
20
  }
15
21
  export declare function loadConfig(cwd?: string): SetupConfig;
22
+ /**
23
+ * Auto-generate .claude-setup.json with sensible defaults.
24
+ * Only creates if it doesn't exist — never overwrites.
25
+ * Returns true if created, false if already existed.
26
+ */
27
+ export declare function ensureConfig(cwd?: string): boolean;
28
+ /** Apply truncation rule to file content */
29
+ export declare function applyTruncation(filename: string, content: string, config: SetupConfig): string;
package/dist/config.js CHANGED
@@ -1,5 +1,18 @@
1
- import { readFileSync, existsSync } from "fs";
1
+ import { readFileSync, writeFileSync, existsSync } from "fs";
2
2
  import { join } from "path";
3
+ // --- Defaults ---
4
+ // Sensible for projects up to ~200 source files.
5
+ // For bigger projects, the developer can increase budgets in .claude-setup.json.
6
+ const DEFAULT_TRUNCATION_RULES = {
7
+ "package-lock.json": { metadataOnly: true },
8
+ "Dockerfile": { maxLines: 50 },
9
+ "docker-compose.yml": { maxLines: 100, maxBytes: 8000 },
10
+ "docker-compose.yaml": { maxLines: 100, maxBytes: 8000 },
11
+ "pom.xml": { maxLines: 80 },
12
+ "build.gradle": { maxLines: 80 },
13
+ "build.gradle.kts": { maxLines: 80 },
14
+ "setup.py": { maxLines: 60 },
15
+ };
3
16
  const DEFAULTS = {
4
17
  maxSourceFiles: 15,
5
18
  maxDepth: 6,
@@ -13,14 +26,21 @@ const DEFAULTS = {
13
26
  digestMode: true,
14
27
  extraBlockedDirs: [],
15
28
  sourceDirs: [],
29
+ truncationRules: DEFAULT_TRUNCATION_RULES,
16
30
  };
17
31
  const CONFIG_FILENAME = ".claude-setup.json";
18
32
  export function loadConfig(cwd = process.cwd()) {
19
33
  const configPath = join(cwd, CONFIG_FILENAME);
20
34
  if (!existsSync(configPath))
21
- return { ...DEFAULTS };
35
+ return { ...DEFAULTS, truncationRules: { ...DEFAULT_TRUNCATION_RULES } };
22
36
  try {
23
37
  const raw = JSON.parse(readFileSync(configPath, "utf8"));
38
+ // Merge truncation rules: user overrides win, defaults fill gaps
39
+ const userRules = raw.truncationRules ?? {};
40
+ const mergedRules = { ...DEFAULT_TRUNCATION_RULES };
41
+ for (const [file, rule] of Object.entries(userRules)) {
42
+ mergedRules[file] = rule;
43
+ }
24
44
  return {
25
45
  maxSourceFiles: raw.maxSourceFiles ?? DEFAULTS.maxSourceFiles,
26
46
  maxDepth: raw.maxDepth ?? DEFAULTS.maxDepth,
@@ -34,9 +54,75 @@ export function loadConfig(cwd = process.cwd()) {
34
54
  digestMode: raw.digestMode ?? DEFAULTS.digestMode,
35
55
  extraBlockedDirs: raw.extraBlockedDirs ?? DEFAULTS.extraBlockedDirs,
36
56
  sourceDirs: raw.sourceDirs ?? DEFAULTS.sourceDirs,
57
+ truncationRules: mergedRules,
37
58
  };
38
59
  }
39
60
  catch {
40
- return { ...DEFAULTS };
61
+ return { ...DEFAULTS, truncationRules: { ...DEFAULT_TRUNCATION_RULES } };
62
+ }
63
+ }
64
+ /**
65
+ * Auto-generate .claude-setup.json with sensible defaults.
66
+ * Only creates if it doesn't exist — never overwrites.
67
+ * Returns true if created, false if already existed.
68
+ */
69
+ export function ensureConfig(cwd = process.cwd()) {
70
+ const configPath = join(cwd, CONFIG_FILENAME);
71
+ if (existsSync(configPath))
72
+ return false;
73
+ const config = {
74
+ maxSourceFiles: DEFAULTS.maxSourceFiles,
75
+ maxDepth: DEFAULTS.maxDepth,
76
+ maxFileSizeKB: DEFAULTS.maxFileSizeKB,
77
+ tokenBudget: DEFAULTS.tokenBudget,
78
+ digestMode: DEFAULTS.digestMode,
79
+ extraBlockedDirs: DEFAULTS.extraBlockedDirs,
80
+ sourceDirs: DEFAULTS.sourceDirs,
81
+ truncationRules: DEFAULT_TRUNCATION_RULES,
82
+ };
83
+ try {
84
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
85
+ return true;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ /** Apply truncation rule to file content */
92
+ export function applyTruncation(filename, content, config) {
93
+ const rule = config.truncationRules[filename];
94
+ if (!rule) {
95
+ // No rule — use generic cap
96
+ return content.length > 4000 ? content.slice(0, 4000) + "\n[... truncated]" : content;
97
+ }
98
+ // Metadata-only extraction (e.g. package-lock.json)
99
+ if (rule.metadataOnly) {
100
+ try {
101
+ const parsed = JSON.parse(content);
102
+ return JSON.stringify({
103
+ name: parsed.name,
104
+ version: parsed.version,
105
+ lockfileVersion: parsed.lockfileVersion,
106
+ }, null, 2);
107
+ }
108
+ catch {
109
+ return `[${filename}: could not parse]`;
110
+ }
111
+ }
112
+ // maxBytes check first: if file is small enough, content passes through before line truncation
113
+ if (rule.maxBytes && content.length <= rule.maxBytes) {
114
+ return content;
115
+ }
116
+ // Line-based truncation
117
+ if (rule.maxLines) {
118
+ const lines = content.split("\n");
119
+ if (lines.length <= rule.maxLines)
120
+ return content;
121
+ return lines.slice(0, rule.maxLines).join("\n") + `\n[... ${lines.length - rule.maxLines} more lines truncated]`;
122
+ }
123
+ // Byte cap
124
+ if (rule.maxBytes && content.length > rule.maxBytes) {
125
+ return content.slice(0, rule.maxBytes) + "\n[... truncated]";
41
126
  }
127
+ return content;
42
128
  }
package/dist/doctor.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function runDoctor(): Promise<void>;
1
+ export declare function runDoctor(verbose?: boolean): Promise<void>;