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.
- package/README.md +96 -45
- package/dist/builder.js +226 -32
- package/dist/collect.js +40 -5
- package/dist/commands/add.js +10 -7
- package/dist/commands/doctor.d.ts +5 -1
- package/dist/commands/doctor.js +5 -1
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/init.js +40 -9
- package/dist/commands/remove.js +2 -1
- package/dist/commands/status.js +123 -11
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +65 -10
- package/dist/config.d.ts +14 -0
- package/dist/config.js +89 -3
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +320 -23
- package/dist/index.js +31 -9
- package/dist/manifest.js +12 -0
- package/dist/os.d.ts +25 -0
- package/dist/os.js +52 -0
- package/dist/output.d.ts +18 -0
- package/dist/output.js +40 -0
- package/dist/state.js +5 -1
- package/package.json +1 -1
- package/templates/add.md +8 -4
- package/templates/init.md +81 -17
- package/templates/remove.md +31 -5
- package/templates/sync.md +28 -13
package/dist/commands/init.js
CHANGED
|
@@ -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 —
|
|
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",
|
|
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
|
|
71
|
+
Runs ${steps.length - 1} atomic steps. If one fails, re-run only that step.
|
|
41
72
|
`);
|
|
42
73
|
}
|
package/dist/commands/remove.js
CHANGED
|
@@ -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
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
console.log(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
console.log(`
|
|
17
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
}
|
package/dist/commands/sync.d.ts
CHANGED
package/dist/commands/sync.js
CHANGED
|
@@ -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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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>;
|