Skip to content
This repository was archived by the owner on Feb 14, 2026. It is now read-only.

Commit 255e27d

Browse files
Chris Arterclaude
andcommitted
Add refdocs init command, auto-init on add, and fix add test buffer slice
Adds `refdocs init` to create .refdocs.json with full defaults, and auto-initializes config when `refdocs add` is called without one. Fixes downloadTarball mock buffer slice in add tests. Bumps version to 0.3.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f761c0f commit 255e27d

7 files changed

Lines changed: 122 additions & 5 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ refdocs/
5757

5858
## CLI Commands
5959

60+
### `refdocs init`
61+
62+
Create a `.refdocs.json` config file with full defaults. Errors if the file already exists. Also auto-runs when `refdocs add` is called without an existing config.
63+
6064
### `refdocs index`
6165

6266
Walk all configured paths, chunk every `.md` file, build and persist the MiniSearch index.

docs/cli-reference.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,36 @@ refdocs --version Show version number
99

1010
---
1111

12+
## `refdocs init`
13+
14+
Create a `.refdocs.json` config file with default settings.
15+
16+
```bash
17+
refdocs init
18+
```
19+
20+
Writes `.refdocs.json` in the current directory with the full default configuration (paths, index location, chunk sizes, boost fields).
21+
22+
**Output:**
23+
24+
```
25+
Created .refdocs.json with default configuration.
26+
```
27+
28+
**Behavior:**
29+
30+
- Creates `.refdocs.json` in the current working directory
31+
- Includes all default values: `paths`, `index`, `chunkMaxTokens`, `chunkMinTokens`, `boostFields`
32+
- If `.refdocs.json` already exists, exits with an error
33+
34+
**Errors:**
35+
36+
- Config already exists: `.refdocs.json already exists in /path/to/dir`
37+
38+
**Note:** Running `refdocs add` will automatically initialize `.refdocs.json` if it doesn't exist.
39+
40+
---
41+
1242
## `refdocs index`
1343

1444
Build the search index from all markdown files in configured paths.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dynamik-dev/refdocs",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"type": "module",
55
"description": "Local CLI tool that indexes markdown documentation and exposes fast fuzzy search with intelligent chunking",
66
"main": "dist/src/index.js",

src/config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ export function validateConfig(raw: unknown): string[] {
107107
return errors;
108108
}
109109

110+
export function configExists(configDir: string): boolean {
111+
return existsSync(join(configDir, CONFIG_FILENAME));
112+
}
113+
114+
export function initConfig(configDir: string): void {
115+
const configPath = join(configDir, CONFIG_FILENAME);
116+
if (existsSync(configPath)) {
117+
throw new Error(`${CONFIG_FILENAME} already exists in ${configDir}`);
118+
}
119+
writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
120+
}
121+
110122
export function saveConfig(config: Partial<RefdocsConfig>, configDir: string): void {
111123
const configPath = join(configDir, CONFIG_FILENAME);
112124
let existing: Record<string, unknown> = {};

src/index.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { Command } from "commander";
44
import { join } from "node:path";
5-
import { loadConfig } from "./config.js";
5+
import { loadConfig, configExists, initConfig } from "./config.js";
66
import { buildIndex, loadPersistedIndex } from "./indexer.js";
77
import { search } from "./search.js";
88
import { addFromUrl, addLocalPath, removePath, updateSources } from "./add.js";
@@ -13,7 +13,20 @@ const program = new Command();
1313
program
1414
.name("refdocs")
1515
.description("Local CLI tool for indexing and searching markdown documentation")
16-
.version("0.1.0");
16+
.version("0.3.0");
17+
18+
program
19+
.command("init")
20+
.description("Create a .refdocs.json config file with defaults")
21+
.action(() => {
22+
try {
23+
initConfig(process.cwd());
24+
console.log("Created .refdocs.json with default configuration.");
25+
} catch (err) {
26+
console.error((err as Error).message);
27+
process.exit(1);
28+
}
29+
});
1730

1831
program
1932
.command("index")
@@ -128,6 +141,11 @@ program
128141
.option("--no-index", "skip auto re-indexing after download")
129142
.action(async (source: string, opts: { path?: string; branch?: string; index: boolean }) => {
130143
try {
144+
const cwd = process.cwd();
145+
if (!configExists(cwd)) {
146+
initConfig(cwd);
147+
console.log("Initialized .refdocs.json with default configuration.");
148+
}
131149
const { config, configDir } = loadConfig();
132150
const isUrl = source.startsWith("http://") || source.startsWith("https://");
133151

tests/add.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ vi.mock("../src/github.js", async (importOriginal) => {
7777
return {
7878
...actual,
7979
downloadTarball: vi.fn(async () => {
80-
return readFileSync(FIXTURE_PATH).buffer;
80+
const buf = readFileSync(FIXTURE_PATH);
81+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
8182
}),
8283
};
8384
});

tests/config.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
22
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
33
import { join } from "node:path";
44
import { tmpdir } from "node:os";
5-
import { loadConfig, validateConfig } from "../src/config.js";
5+
import { readFileSync, existsSync } from "node:fs";
6+
import { loadConfig, validateConfig, configExists, initConfig, CONFIG_FILENAME } from "../src/config.js";
67

78
describe("validateConfig", () => {
89
it("returns no errors for valid config", () => {
@@ -131,3 +132,54 @@ describe("loadConfig", () => {
131132
expect(() => loadConfig(tmpDir)).toThrow("Invalid .refdocs.json");
132133
});
133134
});
135+
136+
describe("configExists", () => {
137+
let tmpDir: string;
138+
139+
beforeEach(() => {
140+
tmpDir = mkdtempSync(join(tmpdir(), "refdocs-test-"));
141+
});
142+
143+
afterEach(() => {
144+
rmSync(tmpDir, { recursive: true, force: true });
145+
});
146+
147+
it("returns false when no config file exists", () => {
148+
expect(configExists(tmpDir)).toBe(false);
149+
});
150+
151+
it("returns true when config file exists", () => {
152+
writeFileSync(join(tmpDir, CONFIG_FILENAME), "{}");
153+
expect(configExists(tmpDir)).toBe(true);
154+
});
155+
});
156+
157+
describe("initConfig", () => {
158+
let tmpDir: string;
159+
160+
beforeEach(() => {
161+
tmpDir = mkdtempSync(join(tmpdir(), "refdocs-test-"));
162+
});
163+
164+
afterEach(() => {
165+
rmSync(tmpDir, { recursive: true, force: true });
166+
});
167+
168+
it("creates .refdocs.json with full defaults", () => {
169+
initConfig(tmpDir);
170+
const configPath = join(tmpDir, CONFIG_FILENAME);
171+
expect(existsSync(configPath)).toBe(true);
172+
173+
const written = JSON.parse(readFileSync(configPath, "utf-8"));
174+
expect(written.paths).toEqual(["ref-docs"]);
175+
expect(written.index).toBe(".refdocs-index.json");
176+
expect(written.chunkMaxTokens).toBe(800);
177+
expect(written.chunkMinTokens).toBe(100);
178+
expect(written.boostFields).toEqual({ title: 2, headings: 1.5, body: 1 });
179+
});
180+
181+
it("throws if config already exists", () => {
182+
writeFileSync(join(tmpDir, CONFIG_FILENAME), "{}");
183+
expect(() => initConfig(tmpDir)).toThrow(".refdocs.json already exists");
184+
});
185+
});

0 commit comments

Comments
 (0)