Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fa7d03b
fix: update tree-sitter language package versions (0.22.x doesn't exist)
NalinDalal Sep 13, 2026
a77687b
locks
NalinDalal Sep 13, 2026
7eac4ba
Merge branch 'nerdev-co:main' into main
NalinDalal Sep 13, 2026
f5fd3dc
fix: pin tree-sitter versions, add build deps to CI, remove broken Co…
NalinDalal Sep 13, 2026
08ef92e
check
NalinDalal Sep 13, 2026
00d66e7
fix: use push:pg for drizzle-kit postgres
NalinDalal Sep 13, 2026
36c3db7
chore: update drizzle-kit to 0.31.10, drizzle-orm to 0.45.2, neon to …
NalinDalal Sep 13, 2026
38de38e
fix: remove @neondatabase/serverless, not needed for local Postgres
NalinDalal Sep 13, 2026
89382c9
check
NalinDalal Sep 13, 2026
9f295d0
fix: remove db:push from CI until schema exists
NalinDalal Sep 13, 2026
ea9faca
fix: add packageManager field for turbo
NalinDalal Sep 13, 2026
5b490d1
fix: resolve all typecheck errors across packages
NalinDalal Sep 13, 2026
adb5193
configs resolved
NalinDalal Sep 13, 2026
831ddab
tests
NalinDalal Sep 13, 2026
c1bd549
configs
NalinDalal Sep 13, 2026
a23aee2
fix: restore agent runtime dependencies
NalinDalal Sep 13, 2026
93ada11
fix(agent): add missing dependencies and fix TypeScript errors
NalinDalal Sep 13, 2026
4dbe992
test
NalinDalal Sep 13, 2026
ebae736
test
NalinDalal Sep 13, 2026
4d2bc92
merge: resolve conflicts in agent package.json and bun.lock
NalinDalal Sep 13, 2026
af64d02
tests
NalinDalal Sep 13, 2026
d1b186a
fix: address CLI security and review findings
NalinDalal Sep 13, 2026
0165e40
correct path
NalinDalal Sep 13, 2026
3d4df8d
fix: run coverage from the tests package
NalinDalal Sep 13, 2026
8bb5f8e
merge: resolve coverageCheck.ts conflict, use .cwd(import.meta.dir)
NalinDalal Sep 13, 2026
f6d3c5c
fix: correct workspace entrypoints and remove inactive configuration
NalinDalal Sep 13, 2026
c5c7aba
tests
NalinDalal Sep 13, 2026
329c695
Merge branch 'main' of github.com:NalinDalal/DriftLock
NalinDalal Sep 13, 2026
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
26 changes: 5 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,20 @@ jobs:

- uses: oven-sh/setup-bun@v2

- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3 make g++

- name: Install dependencies
run: bun install

- name: Push Drizzle schema
run: bun run db:push
env:
DATABASE_URL: postgresql://test:test@localhost:5432/driftlock_test

- name: Type check
run: bun run typecheck

- name: Build
run: bun run build

- name: Lint
run: bun run lint

- name: Unit tests
run: bun run test:unit

Expand All @@ -62,16 +59,3 @@ jobs:
- name: Coverage check
run: bun run test:coverage

coderabbit:
runs-on: blacksmith-2vcpu-ubuntu-2204
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: CodeRabbit Review
uses: coderabbitai/ai-pr-reviewer@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CODERABBIT_API_KEY: ${{ secrets.CODERABBIT_API_KEY }}
23 changes: 14 additions & 9 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,23 @@ bun run --filter @driftlock/cli driftlock test ./repo --timeout 600000
### 3. Compare Snapshots

```bash
# Compare with main branch
# Report working-tree status
bun run --filter @driftlock/cli driftlock diff ./repo

# Compare with main branch
bun run --filter @driftlock/cli driftlock diff ./repo --base main

# Compare with specific branch
bun run --filter @driftlock/cli driftlock diff ./repo --base develop
```

### 4. Generate Fixes
With `--base`, the comparison includes committed, staged, and unstaged changes
to tracked files. Untracked files are included only in the no-base status report.

```bash
# Generate fix suggestions (requires OpenAI API key)
bun run --filter @driftlock/cli driftlock fix ./repo
### 4. Generate Fixes

# With explicit API key
bun run --filter @driftlock/cli driftlock fix ./repo --api-key sk-...
```
The `fix` command is not yet available. It exits with an error until the CLI
implements snapshot comparison, drift analysis, and suggestion generation.

## Development

Expand Down Expand Up @@ -111,10 +112,14 @@ bun run --filter @driftlock/agent test:watch

## Configuration

Supply credentials through the `OPENAI_API_KEY` environment variable using your
shell or CI secret manager. Never put API keys in `.driftlock.yml` or commit them.
The `init` command does not request or store a key. The CLI does not yet load
credentials or other settings from this file.

Create `.driftlock.yml` in your project root:

```yaml
openaiApiKey: sk-...
testCommand: bun test
enableProxy: true
sandbox:
Expand Down
34 changes: 34 additions & 0 deletions apps/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
15 changes: 15 additions & 0 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# cli

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run index.ts
```

This project was created using `bun init` in bun v1.3.11. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
251 changes: 251 additions & 0 deletions apps/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import { Command } from "commander";
import chalk from "chalk";
import ora from "ora";
import inquirer from "inquirer";
import { TypeScriptExtractor } from "@driftlock/parser";
import { SandboxRunner } from "@driftlock/sandbox";
import { GitTracker } from "@driftlock/git";

const program = new Command();

program
.name("driftlock")
.description("API drift detection and fix generation")
.version("0.1.0");

program
.command("analyze")
.description("Analyze codebase for API call sites")
.argument("<path>", "Path to analyze")
.option("-o, --output <format>", "Output format (json, table)", "table")
.action(async (path: string, options: { output: string }) => {
const spinner = ora("Analyzing codebase...").start();

try {
const extractor = new TypeScriptExtractor();
const fs = await import("fs");
const pathModule = await import("path");

// Read all TypeScript files
const files = fs
.readdirSync(path, { recursive: true })
.filter(
(file): file is string =>
typeof file === "string" && file.endsWith(".ts"),
);

const allCallSites = [];
const errors = [];

for (const file of files) {
const filePath = pathModule.join(path, file);
const content = fs.readFileSync(filePath, "utf-8");
const result = await extractor.extractFromFile(
filePath,
content,
);
allCallSites.push(...result.callSites);
errors.push(...result.errors);
}

spinner.succeed(`Found ${allCallSites.length} API call sites`);

if (options.output === "json") {
console.log(
JSON.stringify(
{ callSites: allCallSites, errors },
null,
2,
),
);
} else {
console.log(chalk.bold("\nAPI Call Sites:"));
for (const site of allCallSites) {
console.log(
` ${chalk.cyan(site.filePath)}:${chalk.yellow(site.line)}`,
);
console.log(
` ${chalk.green(site.method)} → ${chalk.blue(site.endpoint)}`,
);
console.log(` HTTP: ${site.httpMethod}`);
console.log("");
}

if (errors.length > 0) {
console.log(chalk.bold.red("\nErrors:"));
for (const error of errors) {
console.log(
` ${chalk.red(error.file)}:${chalk.yellow(error.line)} - ${error.message}`,
);
}
}
}
} catch (error) {
spinner.fail("Analysis failed");
console.error(error);
process.exit(1);
}
});

program
.command("test")
.description("Run tests in sandbox environment")
.argument("<path>", "Path to test")
.option("-c, --command <cmd>", "Test command to run", "npm test")
.option("-t, --timeout <ms>", "Timeout in milliseconds", "300000")
.action(
async (path: string, options: { command: string; timeout: string }) => {
const spinner = ora("Running tests in sandbox...").start();

try {
const runner = new SandboxRunner();
const result = await runner.runTestSuite(path, {
image: "node:20-slim",
command: ["sh", "-c", options.command],
env: {},
timeout: parseInt(options.timeout, 10),
memoryLimit: "512m",
cpuLimit: 1.0,
networkEnabled: false,
allowedEndpoints: [],
});

if (result.exitCode === 0) {
spinner.succeed(`Tests passed (${result.duration}ms)`);
} else {
spinner.fail(
`Tests failed with exit code ${result.exitCode}`,
);
}

console.log(chalk.bold("\nStdout:"));
console.log(result.stdout);

if (result.stderr) {
console.log(chalk.bold.red("\nStderr:"));
console.log(result.stderr);
}

console.log(chalk.bold("\nTraffic Captured:"));
console.log(` ${result.trafficCaptured.length} requests`);
} catch (error) {
spinner.fail("Sandbox test failed");
console.error(error);
process.exit(1);
}
},
);

program
.command("diff")
.description("Compare API snapshots")
.argument("<path>", "Repository path")
.option("-b, --base <branch>", "Base branch to compare")
.action(async (path: string, options: { base?: string }) => {
const spinner = ora("Comparing snapshots...").start();

try {
const tracker = new GitTracker(path);
const changes = await tracker.detectChanges(options.base);

spinner.succeed("Snapshot comparison complete");

console.log(chalk.bold("\nChanges:"));
console.log(
` ${chalk.green("+ Added:")} ${changes.added.length} files`,
);
console.log(
` ${chalk.yellow("~ Modified:")} ${changes.modified.length} files`,
);
console.log(
` ${chalk.red("- Deleted:")} ${changes.deleted.length} files`,
);
console.log(
` ${chalk.blue("→ Renamed:")} ${changes.renamed.length} files`,
);

if (changes.added.length > 0) {
console.log(chalk.bold.green("\nAdded Files:"));
for (const file of changes.added) {
console.log(` + ${file}`);
}
}

if (changes.modified.length > 0) {
console.log(chalk.bold.yellow("\nModified Files:"));
for (const file of changes.modified) {
console.log(` ~ ${file}`);
}
}

if (changes.deleted.length > 0) {
console.log(chalk.bold.red("\nDeleted Files:"));
for (const file of changes.deleted) {
console.log(` - ${file}`);
}
}
} catch (error) {
spinner.fail("Diff comparison failed");
console.error(error);
process.exit(1);
}
});

program
.command("fix")
.description("Generate fix suggestions (not yet available)")
.argument("<path>", "Repository path")
.action(() => {
console.error(
"Fix generation is not yet available: snapshot comparison and drift analysis are not implemented in the CLI.",
);
process.exitCode = 1;
});

program
.command("init")
.description("Initialize DriftLock configuration")
.action(async () => {
const spinner = ora("Initializing DriftLock...").start();

try {
const answers = await inquirer.prompt([
{
type: "input",
name: "testCommand",
message: "Test command:",
default: "npm test",
},
{
type: "confirm",
name: "enableProxy",
message: "Enable HTTPS proxy for traffic capture?",
default: true,
},
]);

// Create .driftlock.yml
const config = `
# Supply credentials through the OPENAI_API_KEY environment variable, never this file.
testCommand: ${answers.testCommand}
enableProxy: ${answers.enableProxy}
sandbox:
image: node:20-slim
memoryLimit: 512m
cpuLimit: 1.0
timeout: 300000
`.trimStart();

const fs = await import("fs");
fs.writeFileSync(".driftlock.yml", config);

spinner.succeed("DriftLock initialized");
console.log(chalk.green("Created .driftlock.yml"));
} catch (error) {
spinner.fail("Initialization failed");
console.error(error);
process.exit(1);
}
});

program.parse();
Loading
Loading