Skip to content
Closed
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
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ Refer to [agents.md](../agents.md) at the repository root for full architecture,
- Tests use `MockEmbeddingProvider` and in-memory SQLite (no sqlite-vec in tests).
- Run `npm run typecheck && npm run test:coverage && npm run lint` before considering work complete. Use `test:coverage` (not `test`) — CI enforces coverage thresholds (statements ≥ 75%, branches ≥ 74%, functions ≥ 75%, lines ≥ 75%) and will reject PRs that drop below them.
- Before creating a PR, use a `code-review` sub-agent to self-review your diff. Fix any issues it finds before opening the PR.
- **Branch workflow:** All feature branches and PRs target `development`. Only `development` can be merged into `main`. When creating branches, branch from `development`. When creating PRs, set the base to `development`.
- **PR lifecycle is mandatory.** After pushing a PR, always: (1) wait for CI/CD to complete, (2) check if it passed, (3) fix failures and re-push if needed, (4) read and address all review comments, (5) verify CI is green again. A PR is not done until all checks pass and all review comments are resolved. See the "Pull Request Lifecycle" section in `agents.md` for the full workflow.
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
target-branch: "development"
schedule:
interval: "weekly"
groups:
minor-and-patch:
update-types: ["minor", "patch"]
- package-ecosystem: "pip"
directory: "/sdk/python"
target-branch: "development"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/sdk/go"
target-branch: "development"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "development"
schedule:
interval: "weekly"
groups:
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CI

on:
push:
branches: [main]
branches: [main, development]
pull_request:
branches: [main]
branches: [main, development]
workflow_call:

concurrency:
Expand All @@ -15,8 +15,8 @@ jobs:
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
Expand All @@ -31,16 +31,16 @@ jobs:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run test:coverage
- name: Upload coverage
if: matrix.node-version == 22
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: coverage
path: coverage/
Expand All @@ -51,7 +51,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
Expand All @@ -60,8 +60,8 @@ jobs:
runs-on: ubuntu-latest
needs: [lint-and-typecheck, test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: "CodeQL"

on:
push:
branches: [main]
branches: [main, development]
pull_request:
branches: [main]
branches: [main, development]
schedule:
- cron: "0 6 * * 1"

Expand All @@ -21,7 +21,7 @@ jobs:
matrix:
language: [javascript-typescript]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Docker
on:
push:
branches: [main]
branches: [main, development]
tags: ["v*"]
pull_request:
paths: ["Dockerfile", "docker-compose.yml", ".dockerignore"]
Expand All @@ -21,7 +21,7 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
if: github.event_name != 'pull_request'
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/merge-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Merge Gate

on:
pull_request:
branches: [main]

jobs:
enforce-source-branch:
runs-on: ubuntu-latest
steps:
- name: Verify PR source is development branch
run: |
if [ "${{ github.head_ref }}" != "development" ]; then
echo "::error::Only the 'development' branch can be merged into main. This PR is from '${{ github.head_ref }}'."
exit 1
fi
echo "✅ Source branch is 'development' — merge allowed."
4 changes: 2 additions & 2 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
environment: pypi
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
needs: ci
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sdk-go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:
matrix:
go-version: ["1.21", "1.22"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go-version }}
- run: cd sdk/go && go test ./... -v
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sdk-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- run: cd sdk/python && pip install -e ".[dev]" && pytest -v
78 changes: 77 additions & 1 deletion agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,32 @@ LibScope is an **AI-powered knowledge base with MCP (Model Context Protocol) int
src/
├── cli/index.ts # CLI entry point (commander). All commands in one file.
├── mcp/server.ts # MCP server (stdio transport, @modelcontextprotocol/sdk)
├── api/
│ ├── server.ts # HTTP server bootstrap (createServer, listen)
│ ├── routes.ts # All REST route handlers in one function (~700 lines)
│ ├── middleware.ts # Auth (checkApiKey), rate limiting, body parsing, sendError/sendJson
│ └── openapi.ts # OpenAPI spec generation
├── core/ # Business logic — framework-agnostic, no side effects
│ ├── indexing.ts # Document parsing, chunking by heading, embedding + storage
│ ├── search.ts # Semantic (vector) + FTS5 + LIKE fallback search
│ ├── ratings.ts # Rating storage, aggregation, correction suggestions
│ ├── documents.ts # Document CRUD
│ ├── topics.ts # Topic hierarchy management
│ ├── url-fetcher.ts # Fetch URL → convert HTML to markdown-like text
│ ├── bulk.ts # Bulk delete/move/retag operations with selector resolution
│ ├── tags.ts # Tag CRUD and document–tag associations
│ ├── dedup.ts # Content deduplication helpers
│ ├── rag.ts # Retrieval-augmented generation (ask a question over the index)
│ ├── url-fetcher.ts # Fetch URL → convert HTML to markdown-like text (SSRF-protected)
│ └── index.ts # Public re-exports (barrel file)
├── connectors/ # External-service sync connectors
│ ├── obsidian.ts # Obsidian vault sync (reads .md files + YAML frontmatter)
│ ├── confluence.ts # Confluence Cloud sync (REST API)
│ ├── notion.ts # Notion sync (official API)
│ ├── onenote.ts # OneNote sync (Microsoft Graph API)
│ ├── slack.ts # Slack channel sync (Web API)
│ ├── sync-tracker.ts # Tracks last-synced state per connector in SQLite
│ ├── http-utils.ts # Authenticated fetch helper shared by connectors (respects allowSelfSignedCerts)
│ └── index.ts # Re-exports
├── db/
│ ├── connection.ts # SQLite connection + sqlite-vec extension loading
│ ├── schema.ts # Migrations (versioned) + vector table creation
Expand Down Expand Up @@ -146,6 +164,64 @@ Environment variables > project `.libscope.json` > user `~/.libscope/config.json

Env vars: `LIBSCOPE_EMBEDDING_PROVIDER`, `LIBSCOPE_OPENAI_API_KEY`, `LIBSCOPE_OLLAMA_URL`, `LIBSCOPE_ALLOW_PRIVATE_URLS`, `LIBSCOPE_ALLOW_SELF_SIGNED_CERTS`.

## Security Patterns

### Authentication — use constant-time comparison

The API key check in `src/api/middleware.ts` must use `crypto.timingSafeEqual` to prevent timing attacks. Direct string equality (`===` / `!==`) short-circuits on the first differing byte, leaking information about the key length and value.

```typescript
import { timingSafeEqual } from "node:crypto";

// ✅ Correct
const tokenBuf = Buffer.from(token);
const keyBuf = Buffer.from(apiKey);
if (tokenBuf.length !== keyBuf.length || !timingSafeEqual(tokenBuf, keyBuf)) {
return sendError(res, 401, "UNAUTHORIZED", "Invalid API key");
}

// ❌ Wrong — timing-attack vulnerable
if (token !== apiKey) { ... }
```

### TLS — use per-request undici Agent, not process-wide env var

When self-signed certificates must be accepted (controlled by `config.indexing.allowSelfSignedCerts`), configure TLS per-request via an `undici.Agent` passed as `dispatcher`. Do **not** mutate `process.env["NODE_TLS_REJECT_UNAUTHORIZED"]` — it is process-global and creates a race condition with concurrent requests.

```typescript
import { Agent } from "undici";

// ✅ Correct — scoped to this request chain only
let _insecureAgent: Agent | undefined;
const getInsecureAgent = (): Agent =>
(_insecureAgent ??= new Agent({ connect: { rejectUnauthorized: false } }));

const response = await fetch(url, {
// @ts-expect-error — Node.js undici-based fetch accepts dispatcher for per-request TLS config
dispatcher: allowSelfSigned ? getInsecureAgent() : undefined,
});

// ❌ Wrong — affects all concurrent requests until restored
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
```

### SSE streaming — check backpressure

`res.write()` returns `false` when the socket buffer is full or the client has disconnected. Ignoring the return value wastes compute and holds open connections indefinitely.

```typescript
// ✅ Correct
for await (const event of stream) {
const ok = res.write(`data: ${JSON.stringify(event)}\n\n`);
if (!ok) break;
}

// ❌ Wrong — no backpressure handling
for await (const event of stream) {
res.write(`data: ${JSON.stringify(event)}\n\n`);
}
```

## Testing

### Framework
Expand Down
Loading
Loading