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
13 changes: 13 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env sh
# DeepCode pre-commit hook — runs typecheck + tests on the affected packages.
# Skip with `git commit --no-verify` (you'll own the consequences).

set -e

echo "▎ pre-commit: typecheck"
pnpm -r typecheck

echo "▎ pre-commit: tests"
pnpm -r test

echo "✓ pre-commit checks passed"
74 changes: 74 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// ESLint flat config — DeepCode monorepo.
// Spec: docs/DEVELOPMENT_PLAN.md §0 (engineering hygiene)
//
// Keep this list minimal: code style is enforced by Prettier; ESLint catches
// correctness issues only. Type-aware rules pull from each package's tsconfig.

import js from '@eslint/js';
import tseslint from 'typescript-eslint';

export default [
{
ignores: [
'**/dist/**',
'**/dist-electron/**',
'**/node_modules/**',
'**/.tsbuildinfo',
'apps/desktop/electron/**', // requires electron types — pending M6-rest
],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: {
process: 'readonly',
console: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
URL: 'readonly',
URLSearchParams: 'readonly',
AbortController: 'readonly',
AbortSignal: 'readonly',
TextEncoder: 'readonly',
TextDecoder: 'readonly',
fetch: 'readonly',
crypto: 'readonly',
},
},
rules: {
// Disable rules that conflict with current pragmatic patterns:
'@typescript-eslint/no-explicit-any': 'off', // we use `unknown` cast pattern
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'no-empty': ['warn', { allowEmptyCatch: true }],
'no-constant-condition': ['warn', { checkLoops: false }],
'no-async-promise-executor': 'off', // SessionManager uses this pattern
// Tests deliberately use any
'no-unused-vars': 'off',
},
},
{
// Tests can be looser
files: ['**/*.test.ts', '**/*.test.tsx'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
},
},
];
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"description": "DeepCode — Claude Code 的 DeepSeek 版(monorepo root)",
"license": "MIT",
"type": "module",
"engines": {
"node": ">=22",
"pnpm": ">=9"
Expand All @@ -17,15 +18,21 @@
"build": "tsc -b",
"typecheck": "tsc -b --pretty",
"test": "pnpm -r test",
"lint": "pnpm -r lint",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"**/*.{ts,tsx,json,md,yml,yaml}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,json,md,yml,yaml}\"",
"clean": "pnpm -r clean"
"clean": "pnpm -r clean",
"prepare": "husky || true"
},
"devDependencies": {
"@eslint/js": "^9.16.0",
"@types/node": "^22.10.0",
"eslint": "^9.16.0",
"husky": "^9.1.0",
"prettier": "^3.3.0",
"typescript": "^5.7.0",
"typescript-eslint": "^8.18.0",
"vitest": "^2.1.0"
}
}
53 changes: 51 additions & 2 deletions packages/core/skills/code-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,57 @@ description: Review the current diff for correctness bugs. Cite file:line.

# code-review

(M4 baseline — body authored as quick guidance; can be expanded by the user via `/skill edit` or directly editing this file.)
Read the diff, find correctness problems, and explain each with a precise
`file:line` reference. Style/formatting is out of scope unless the user
explicitly asks — Prettier and the team's own conventions own that.

## When to invoke

The agent should invoke this skill when the user request matches the description.
- User says "review this", "look this over", "second opinion".
- Right after the user has made a non-trivial change and asks for feedback
before pushing.
- As part of `/security-review` (see that skill for the security-specific
checklist).

## Process

1. **Read the diff** — use `Bash` to run `git diff` (or
`git diff origin/main...HEAD` if the user is on a feature branch).
2. **Read the touched files in full** — diff context is too narrow to
judge a function. Pull the whole file via `Read`.
3. **Read tests** — confirm new behavior is covered; flag missing tests.
4. **Categorize findings**:
- **Bug** — code would observably misbehave.
- **Latent bug** — works today but is fragile / racy / order-dependent.
- **Suggestion** — cleaner alternative; not a defect.
5. **Cite precisely** — every finding gets `path/to/file.ts:42` so the
user can jump to it.

## Heuristics for finding bugs

- **Off-by-one**: `i < arr.length` vs `<=`; `slice(start, end)` boundaries.
- **Null-safety**: optional chaining missed (`a.b.c` when `b` can be null).
- **Async**: missed `await`, fire-and-forget promise without `.catch()`.
- **Error handling**: catch blocks that swallow then continue with bad state.
- **State**: mutation that escapes the function via a shared reference.
- **Concurrency**: writes to the same file/key/row without locking.
- **Cleanup**: `try`/`finally` missing for `unlink`, `kill`, `close`.
- **Security**: shell injection, path traversal, secrets in logs.

## What NOT to do

- Don't paraphrase the diff back at the user — they wrote it.
- Don't restate findings in 4 different ways. One bullet, one cite, one fix.
- Don't ask "did you mean X?" — say "this is wrong because X; change to Y".

## Output shape

```
N findings:

· BUG src/foo.ts:42 — <description>. <suggested fix>.
· LATENT src/bar.ts:88 — <description>. Suggest <fix>.
· TEST GAP src/baz.ts:120 — <name> is uncovered. Add a test for the <case> path.

Overall: <one-sentence verdict>.
```
50 changes: 47 additions & 3 deletions packages/core/skills/init/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,56 @@
---
name: init
description: Generate a starter DEEPCODE.md after exploring the codebase.
description: Generate a starter AGENTS.md after exploring the codebase.
---

# init

(M4 baseline — body authored as quick guidance; can be expanded by the user via `/skill edit` or directly editing this file.)
Bootstrap a new project's `AGENTS.md` (DeepCode's per-project agent
instructions file). The skill is normally triggered by the `/init` slash
command, but you can also invoke it implicitly when the user asks for
project documentation.

## When to invoke

The agent should invoke this skill when the user request matches the description.
- User runs `/init` in a project without an existing `AGENTS.md`.
- User says "set up AGENTS.md for this project" or similar.
- A fresh clone is opened and the agent notices the missing file (see
the `agents-md-missing` system reminder).

## What to produce

A markdown file at `<cwd>/AGENTS.md` covering:

1. **Project name + one-line description** — derived from `package.json`,
`pyproject.toml`, `Cargo.toml`, etc.
2. **Tech stack** — language / framework / package manager.
3. **Install / build / test commands** — exact invocations
(`pnpm install && pnpm test`, `cargo build`, etc.).
4. **Code-style conventions** — formatter (Prettier / Black / rustfmt),
line length, import ordering — whatever's discernible.
5. **Entry points** — where `main.ts` / `__main__.py` / `cmd/cli/main.go`
lives; which package owns the public API.
6. **Do / don't notes** — anything that would bite a new contributor:
"don't run `prisma migrate dev` against prod", "always update both
`core` and the SDK clones in lockstep", etc.

Keep it under 80 lines. Concrete > comprehensive.

## How to invoke (multi-phase flow)

The `/init` slash command in DeepCode walks three phases:

1. **Scan** — list top-level entries, read up to 30 lines each of
`package.json` / `README.md` / `pyproject.toml` / `Cargo.toml` / `go.mod`.
2. **Propose** — single LLM call with the scan as context; output is the
`AGENTS.md` markdown only (no preface, no fences).
3. **Approve** — show the first 40 lines, prompt `y/n`. On `y`, write to
`<cwd>/AGENTS.md`.

## Failure modes

- Empty project (no manifest) → write a stub with just sections 1, 2, 6.
- Existing `AGENTS.md` → ask the user before overwriting.
- LLM returns prose around the markdown → strip code fences if any; if
output is empty, write a `# AGENTS.md\n\n(Empty draft.)` so the file
still exists for the user to fill.
79 changes: 77 additions & 2 deletions packages/core/skills/security-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,83 @@ description: Review pending changes for security issues (secrets, injections, SS

# security-review

(M4 baseline — body authored as quick guidance; can be expanded by the user via `/skill edit` or directly editing this file.)
Specialized review focusing on security regressions. Goes deeper than
`code-review` on these specific dimensions; ignores style/perf.

## When to invoke

The agent should invoke this skill when the user request matches the description.
- User says "security review", "look for vulnerabilities", "before deploy".
- Touched code is in: auth, sessions, file paths, HTTP fetches, subprocess
exec, deserialization, template rendering, secrets handling.
- New external input enters the system (form fields, URL params, headers,
webhook payloads, file uploads).

## Categories to check

### Secrets

- Hard-coded API keys / DB passwords / signing keys in source.
- Secret logged to stdout/stderr / written to disk un-redacted.
- Secret committed to git history (search with `git log -p -S '<prefix>'`).
- Environment variable leaked into a subprocess that doesn't need it.

### Injection

- **Shell**: `Bash` with user-controlled string concatenation — use argv
arrays. Look for `\`${userInput}\`` in commands.
- **SQL**: parametrized? Or string concat into a query?
- **NoSQL**: `$where` / `$expr` / similar with user input.
- **Template**: SSTI in Jinja/EJS/Handlebars from user input.
- **LDAP / XPath / regex**: user input embedded into a query without escape.

### Path traversal

- File operations that join user input into a path — `path.join(uploadDir, userFileName)`
is exploitable if `userFileName = '../../etc/passwd'`. Resolve + verify
the result still starts with the expected prefix.

### SSRF

- `fetch(userUrl)` without an allowlist or denylist for internal addresses
(169.254.169.254, 127.0.0.1, link-local IPv6).
- Image fetch / preview generation from user URLs (especially!).
- `redirect: 'follow'` lets an attacker hop from a benign host to internal.

### Auth / authz

- Missing auth check on a new endpoint.
- Authorization via user-supplied ID without ownership verification.
- Session fixation: session ID not rotated after login.
- JWT: `alg: none` accepted, or wrong key used for verification.
- CSRF: state-changing GET, or missing token check.

### Crypto

- `Math.random()` for tokens — use `crypto.randomBytes`.
- Custom encryption — flag for review by a domain expert.
- Comparing secrets with `===` (timing attack) — use `crypto.timingSafeEqual`.

### Deserialization

- `JSON.parse` of untrusted input → mostly fine, but watch for prototype
pollution (`__proto__`, `constructor.prototype`).
- `eval` / `Function()` / `vm.runInThisContext` with user input — never.
- `yaml.load` (not `safeLoad`), `pickle.loads`, `unserialize` — all dangerous.

## Reporting

Same shape as `code-review` but tag each finding with severity:

```
N findings:

· HIGH src/api/users.ts:42 — Missing authz check; any logged-in user
can read any other user's data. Add `requireOwner(req, user.id)`.
· MEDIUM src/upload.ts:88 — Path-traversal: user-controlled filename
joined to dir without resolve+prefix check.
· LOW src/util/random.ts:12 — Math.random for session token. Use
crypto.randomBytes(32).toString('hex').

Severity guide: HIGH = exploitable today; MEDIUM = exploitable with
attacker effort; LOW = defense-in-depth gap.
```
34 changes: 32 additions & 2 deletions packages/core/skills/verify/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,38 @@ description: Run the app + tests + confirm the change actually works (not just u

# verify

(M4 baseline — body authored as quick guidance; can be expanded by the user via `/skill edit` or directly editing this file.)
Don't declare a task complete just because unit tests pass. Actually run the
code path the user asked about and confirm the observable behavior.

## When to invoke

The agent should invoke this skill when the user request matches the description.
- After making code changes that affect a runtime path.
- Before announcing "done" / opening a PR / asking the user to test.
- When tests are absent or thin, but the change is non-trivial.

## What "verify" means concretely

| Change type | Verify by |
| ---------------------------- | ---------------------------------------------------------------------------- |
| New CLI flag / subcommand | Run the binary with the flag; confirm exit code + stdout. |
| Bug fix in a function | Add (or run) a test that reproduces the bug + passes after the fix. |
| Refactor of internal API | Run the full test suite + grep for remaining old-name callers. |
| Schema migration | Apply forward + backward on a fresh DB; confirm `\d` matches. |
| HTTP endpoint added | `curl localhost:<port>/<path>` and inspect the response. |
| Background task / cron | Trigger the entry point manually; check the log file or queue. |
| UI change | Take a screenshot via `mcp__computer-use__screenshot` OR ask the user. |

## Anti-patterns

- "Tests pass, so it's done." — only when the test actually covers the
user's reported behavior.
- "It compiled." — type-check is necessary, not sufficient.
- "It works on my machine." — note the assumption; flag for the user to
verify on theirs.

## What to report back

A concise paragraph naming **what you ran**, **what you observed**, and
whether the observation matches the user's intent. If you can't fully
verify (e.g. requires production data), say so explicitly and propose
the minimum the user has to do.
2 changes: 1 addition & 1 deletion packages/core/src/harness/tool-dispatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('dispatchToolCall', () => {
});

it('mode=plan blocks write tools (short-circuit, hook does not fire)', async () => {
let hookFired = false;
// hookFired flag retained for documentation but not asserted directly
const hooks = new HookDispatcher({
hooks: {
PreToolUse: [{ hooks: [{ type: 'command', command: 'echo hook' }] }],
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/plugins/marketplace.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { generateKeyPairSync, sign } from 'node:crypto';
import { promises as fs } from 'node:fs';
import { createServer, type Server } from 'node:http';
import { AddressInfo } from 'node:net';
import { mkdtemp, rm } from 'node:fs/promises';
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/plugins/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// The trust ladder uses these to color a plugin as "official" / "marketplace"
// / "user-local" / "untrusted".

import { verify } from 'node:crypto';
import { createPublicKey, verify } from 'node:crypto';
import { promises as fs } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
Expand Down Expand Up @@ -68,7 +68,6 @@ export function verifyEntrySignature(entry: MarketplaceEntry): boolean {
// node:crypto ed25519 verify requires a KeyObject — derive from raw pubkey
// bytes wrapped in SPKI. The published pubkey is itself DER-SPKI base64.
const pubKeyDer = Buffer.from(entry.publisherPubKey, 'base64');
const { createPublicKey } = require('node:crypto') as typeof import('node:crypto');
const pub = createPublicKey({ key: pubKeyDer, format: 'der', type: 'spki' });
return verify(null, payload, pub, sig);
} catch {
Expand Down
Loading
Loading