Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @butttons/dora

## 1.4.5

### Patch Changes

- Add `--language` flag to `dora init` for explicit language specification (typescript, javascript, python, rust, go, java)
- Optimize document processing performance and fix `--ignore` flag handling
- Refactor multi-parameter functions to use object parameters for better readability and maintainability

## 1.4.4

### Patch Changes
Expand Down
64 changes: 64 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,70 @@ export function getDependencies(db: Database, path: string): DependencyNode[] {

**Note:** Generated files (like `scip_pb.ts`) are exempt from these rules.

### Function Parameters

Functions with more than one parameter must use a single object parameter instead of multiple positional parameters.

**Rules:**

1. **Single parameter functions** - Can use a simple parameter type
2. **Multiple parameters** - Must use a single object parameter with named properties

**Good:**

```typescript
// Single parameter - OK
function getSymbol(id: number): Symbol {
// Implementation
}

// Multiple parameters - use object
function createDocument(params: {
path: string;
type: string;
content: string;
mtime: number;
}): Document {
// Implementation
}

// Better with type alias
type CreateDocumentParams = {
path: string;
type: string;
content: string;
mtime: number;
};

function createDocument(params: CreateDocumentParams): Document {
// Implementation
}
```

**Bad:**

```typescript
// BAD - Multiple positional parameters
function createDocument(
path: string,
type: string,
content: string,
mtime: number,
): Document {
// Implementation
}

// BAD - Two or more parameters
function batchInsert(
db: Database,
table: string,
columns: string[],
rows: Array<Array<string | number>>,
): void {
// Implementation
}
```

## Directory Structure

Note: Example scip files in `example` folder
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@butttons/dora",
"version": "1.4.4",
"version": "1.4.5",
"module": "src/index.ts",
"type": "module",
"private": true,
Expand Down
8 changes: 4 additions & 4 deletions src/commands/adventure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
export async function adventure(from: string, to: string): Promise<void> {
const ctx = await setupCommand();

const fromPath = resolveAndValidatePath(ctx, from);
const toPath = resolveAndValidatePath(ctx, to);
const fromPath = resolveAndValidatePath({ ctx, inputPath: from });
const toPath = resolveAndValidatePath({ ctx, inputPath: to });

// If same file, return direct path
if (fromPath === toPath) {
Expand Down Expand Up @@ -73,7 +73,7 @@ function findShortestPath(
}

// Get reverse dependencies from 'to' file
const reverseDeps = getReverseDependencies(db, to, depth);
const reverseDeps = getReverseDependencies({ db, relativePath: to, depth });
const reverseSet = new Set(reverseDeps.map((d) => d.path));

// Check if 'from' is in reverse dependencies
Expand Down Expand Up @@ -142,7 +142,7 @@ function reconstructPath(
// Get neighbors
const neighbors = forward
? getDependencies(db, current.file, 1)
: getReverseDependencies(db, current.file, 1);
: getReverseDependencies({ db, relativePath: current.file, depth: 1 });

for (const neighbor of neighbors) {
if (!visited.has(neighbor.path)) {
Expand Down
6 changes: 5 additions & 1 deletion src/commands/changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export async function changes(

for (const file of changedFiles) {
try {
const rdeps = getReverseDependencies(ctx.db, file, DEFAULTS.DEPTH);
const rdeps = getReverseDependencies({
db: ctx.db,
relativePath: file,
depth: DEFAULTS.DEPTH,
});
rdeps.forEach((dep) => impacted.add(dep.path));
} catch {}
}
Expand Down
6 changes: 5 additions & 1 deletion src/commands/complexity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export async function complexity(
): Promise<void> {
const { db } = await setupCommand();

const sortBy = parseStringFlag(flags, "sort", "complexity");
const sortBy = parseStringFlag({
flags,
key: "sort",
defaultValue: "complexity",
});

// Validate sort option
if (!["complexity", "symbols", "stability"].includes(sortBy)) {
Expand Down
108 changes: 54 additions & 54 deletions src/commands/cookbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,72 @@ import { getDoraDir } from "../utils/paths.ts";
import { outputJson } from "./shared.ts";

type CookbookOptions = {
format?: "json" | "markdown";
format?: "json" | "markdown";
};

function getAvailableRecipes(cookbookDir: string): string[] {
try {
const files = readdirSync(cookbookDir);
return files
.filter((file) => file.endsWith(".md") && file !== "index.md")
.map((file) => file.replace(".md", ""))
.sort();
} catch {
return [];
}
try {
const files = readdirSync(cookbookDir);
return files
.filter((file) => file.endsWith(".md") && file !== "index.md")
.map((file) => file.replace(".md", ""))
.sort();
} catch {
return [];
}
}

export async function cookbookList(
options: CookbookOptions = {}
options: CookbookOptions = {},
): Promise<void> {
const config = await loadConfig();
const cookbookDir = join(getDoraDir(config.root), "cookbook");
const format = options.format || "json";
const recipes = getAvailableRecipes(cookbookDir);
const config = await loadConfig();
const cookbookDir = join(getDoraDir(config.root), "cookbook");
const format = options.format || "json";
const recipes = getAvailableRecipes(cookbookDir);

if (format === "markdown") {
console.log("Available recipes:\n");
for (const r of recipes) {
console.log(` - ${r}`);
}
console.log("\nView a recipe: dora cookbook show <recipe>");
console.log("Example: dora cookbook show quickstart");
} else {
outputJson({
recipes,
total: recipes.length,
});
}
if (format === "markdown") {
console.log("Available recipes:\n");
for (const r of recipes) {
console.log(` - ${r}`);
}
console.log("\nView a recipe: dora cookbook show <recipe>");
console.log("Example: dora cookbook show quickstart");
} else {
outputJson({
recipes,
total: recipes.length,
});
}
}

export async function cookbookShow(
recipe: string = "",
options: CookbookOptions = {}
recipe: string = "",
options: CookbookOptions = {},
): Promise<void> {
const config = await loadConfig();
const cookbookDir = join(getDoraDir(config.root), "cookbook");
const format = options.format || "json";
const templateName = recipe ? `${recipe}.md` : "index.md";
const templatePath = join(cookbookDir, templateName);
const config = await loadConfig();
const cookbookDir = join(getDoraDir(config.root), "cookbook");
const format = options.format || "json";
const templateName = recipe ? `${recipe}.md` : "index.md";
const templatePath = join(cookbookDir, templateName);

try {
const content = readFileSync(templatePath, "utf-8");
try {
const content = readFileSync(templatePath, "utf-8");

if (format === "markdown") {
console.log(content.trim());
} else {
outputJson({
recipe: recipe || "index",
content: content.trim(),
});
}
} catch (error) {
if (error instanceof Error && error.message.includes("ENOENT")) {
const availableRecipes = getAvailableRecipes(cookbookDir);
throw new Error(
`Recipe '${recipe}' not found. Available recipes: ${availableRecipes.join(", ")}\n\nCookbook files missing. Run 'dora init' to restore them.`
);
}
throw error;
}
if (format === "markdown") {
console.log(content.trim());
} else {
outputJson({
recipe: recipe || "index",
content: content.trim(),
});
}
} catch (error) {
if (error instanceof Error && error.message.includes("ENOENT")) {
const availableRecipes = getAvailableRecipes(cookbookDir);
throw new Error(
`Recipe '${recipe}' not found. Available recipes: ${availableRecipes.join(", ")}\n\nCookbook files missing. Run 'dora init' to restore them.`,
);
}
throw error;
}
}
2 changes: 1 addition & 1 deletion src/commands/coupling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export async function coupling(
): Promise<void> {
const { db } = await setupCommand();

const threshold = parseIntFlag(flags, "threshold", 5);
const threshold = parseIntFlag({ flags, key: "threshold", defaultValue: 5 });

// Get coupled files
const coupledFiles = getCoupledFiles(db, threshold);
Expand Down
2 changes: 1 addition & 1 deletion src/commands/cycles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export async function cycles(
): Promise<void> {
const { db } = await setupCommand();

const limit = parseIntFlag(flags, "limit", 50);
const limit = parseIntFlag({ flags, key: "limit", defaultValue: 50 });

// Get bidirectional dependencies
const cyclesList = getCycles(db, limit);
Expand Down
8 changes: 6 additions & 2 deletions src/commands/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ export async function deps(
flags: Record<string, string | boolean> = {},
): Promise<void> {
const ctx = await setupCommand();
const depth = parseIntFlag(flags, "depth", DEFAULTS.DEPTH);
const relativePath = resolveAndValidatePath(ctx, path);
const depth = parseIntFlag({
flags,
key: "depth",
defaultValue: DEFAULTS.DEPTH,
});
const relativePath = resolveAndValidatePath({ ctx, inputPath: path });

const dependencies = getDependencies(ctx.db, relativePath, depth);

Expand Down
6 changes: 5 additions & 1 deletion src/commands/docs/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ export async function docsSearch(
): Promise<void> {
const ctx = await setupCommand();
const db = ctx.db;
const limit = parseIntFlag(flags, "limit", DEFAULT_LIMIT);
const limit = parseIntFlag({
flags,
key: "limit",
defaultValue: DEFAULT_LIMIT,
});

if (limit <= 0) {
throw new Error("Limit must be a positive number");
Expand Down
4 changes: 2 additions & 2 deletions src/commands/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export async function exports(
const ctx = await setupCommand();

// Try as file path first
const relativePath = resolvePath(ctx, target);
const relativePath = resolvePath({ ctx: { ctx, inputPath: target } });

if (fileExists(ctx.db, relativePath)) {
if (fileExists({ db: ctx.db, relativePath })) {
const exportedSymbols = getFileExports(ctx.db, relativePath);
if (exportedSymbols.length > 0) {
const result: ExportsResult = {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { outputJson, resolveAndValidatePath, setupCommand } from "./shared.ts";

export async function file(path: string): Promise<void> {
const ctx = await setupCommand();
const relativePath = resolveAndValidatePath(ctx, path);
const relativePath = resolveAndValidatePath({ ctx, inputPath: path });

const symbols = getFileSymbols(ctx.db, relativePath);
const depends_on = getFileDependencies(ctx.db, relativePath);
Expand Down
16 changes: 12 additions & 4 deletions src/commands/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ export async function graph(
flags: Record<string, string | boolean> = {},
): Promise<void> {
const ctx = await setupCommand();
const depth = parseIntFlag(flags, "depth", DEFAULTS.DEPTH);
const direction = parseStringFlag(flags, "direction", "both");
const depth = parseIntFlag({
flags,
key: "depth",
defaultValue: DEFAULTS.DEPTH,
});
const direction = parseStringFlag({
flags,
key: "direction",
defaultValue: "both",
});

if (
!VALID_DIRECTIONS.includes(direction as (typeof VALID_DIRECTIONS)[number])
Expand All @@ -28,7 +36,7 @@ export async function graph(
);
}

const relativePath = resolveAndValidatePath(ctx, path);
const relativePath = resolveAndValidatePath({ ctx, inputPath: path });

// Build graph
const nodes = new Set<string>();
Expand All @@ -45,7 +53,7 @@ export async function graph(
}

if (direction === "rdeps" || direction === "both") {
const rdeps = getReverseDependencies(ctx.db, relativePath, depth);
const rdeps = getReverseDependencies({ db: ctx.db, relativePath, depth });
rdeps.forEach((rdep) => {
nodes.add(rdep.path);
edges.push({ from: rdep.path, to: relativePath });
Expand Down
2 changes: 1 addition & 1 deletion src/commands/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export async function imports(
_flags: Record<string, string | boolean> = {},
): Promise<void> {
const ctx = await setupCommand();
const relativePath = resolveAndValidatePath(ctx, path);
const relativePath = resolveAndValidatePath({ ctx, inputPath: path });

const importsList = getFileImports(ctx.db, relativePath);

Expand Down
Loading