Compare commits

...

4 Commits

Author SHA1 Message Date
xingyue 9102c6698a chore: remove gitea-access rule from project (belongs in agent local skills) 2026-04-23 15:03:14 +08:00
xingyue 6cc8833b2a chore: add cursor rules and annotate legitimate dynamic imports
- Add .cursor/rules/no-dynamic-import.mdc: ban dynamic import() in
  production code with documented exceptions
- Add .cursor/rules/gitea-access.mdc: tea CLI usage guide
- Add explanatory comments on the 2 legitimate dynamic imports in
  sense-runtime.ts and workflow-worker.ts
2026-04-23 15:00:07 +08:00
xingyue 787e791aba refactor(cli): replace dynamic imports with static imports
Convert 6 unnecessary `await import()` calls for Node built-in modules
(node:child_process, node:util) and project modules (../workspace.js)
to static top-level imports in init.ts and start.ts.

Closes #57
2026-04-23 14:52:18 +08:00
xiaomo 781f571474 Merge pull request 'refactor: add daemon subcommand group and dev foreground mode' (#54) from refactor/daemon-subcommand into main 2026-04-23 04:24:31 +00:00
5 changed files with 42 additions and 10 deletions
+34
View File
@@ -0,0 +1,34 @@
---
description: Ban dynamic import() in production code — use static imports instead
globs: packages/*/src/**/*.ts
alwaysApply: true
---
# No Dynamic Import in Production Code
## Rule
Do NOT use `await import()` or dynamic `import()` expressions in production source code.
Always use static top-level `import` statements.
## Why
- Static imports enable tree-shaking and bundler optimizations
- They make dependencies explicit and discoverable at a glance
- Dynamic imports of Node built-ins or project modules add unnecessary async overhead
## Exceptions (must include a comment explaining why)
1. **`sense-runtime.ts`** — loads user-authored sense modules whose paths are only known at runtime
2. **`workflow-worker.ts`** — loads user-authored workflow modules whose paths are only known at runtime
When suppressing, add a comment directly above:
```ts
// Dynamic import required: user module path resolved at runtime
const mod = await import(senseIndexPath);
```
## Test Files
Test files (`__tests__/**`) are exempt — dynamic import after `vi.mock()` is standard vitest practice.
+4 -8
View File
@@ -1,5 +1,7 @@
import { spawn, execFile } from "node:child_process";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { defineCommand } from "citty";
@@ -42,6 +44,8 @@ const GITIGNORE = `data/
node_modules/
`;
const execFileAsync = promisify(execFile);
const CPU_SCHEMA_TS = `import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const cpuUsage = sqliteTable("cpu_usage", {
@@ -90,7 +94,6 @@ function writeFile(filePath: string, content: string): void {
}
async function runCommand(cmd: string, args: string[], cwd: string): Promise<void> {
const { spawn } = await import("node:child_process");
await new Promise<void>((resolve, reject) => {
const child = spawn(cmd, args, { cwd, stdio: "inherit" });
child.on("close", (code) => {
@@ -102,10 +105,6 @@ async function runCommand(cmd: string, args: string[], cwd: string): Promise<voi
}
async function detectPackageManager(): Promise<{ cmd: string; installArgs: string[] }> {
const { execFile } = await import("node:child_process");
const { promisify } = await import("node:util");
const execFileAsync = promisify(execFile);
for (const pm of ["pnpm", "yarn", "npm"]) {
try {
await execFileAsync(pm, ["--version"]);
@@ -223,9 +222,6 @@ async function tryRequireSqlite(nerveRoot: string): Promise<boolean> {
try {
const modulePath = join(nerveRoot, "node_modules", "better-sqlite3");
// Use a child process to test if the native module loads
const { execFile } = await import("node:child_process");
const { promisify } = await import("node:util");
const execFileAsync = promisify(execFile);
await execFileAsync("node", ["-e", `require(${JSON.stringify(modulePath)})`], {
cwd: nerveRoot,
timeout: 10_000,
+2 -2
View File
@@ -1,3 +1,4 @@
import { spawn } from "node:child_process";
import { createWriteStream, existsSync } from "node:fs";
import { mkdir } from "node:fs/promises";
import { dirname, join } from "node:path";
@@ -8,6 +9,7 @@ import { defineCommand } from "citty";
import {
getLogPath,
getNerveRoot,
getSocketPath,
isRunning,
readPidFile,
removePidFile,
@@ -64,7 +66,6 @@ async function runDaemon(nerveRoot: string): Promise<void> {
const logPath = getLogPath();
await mkdir(join(nerveRoot, "logs"), { recursive: true });
const { spawn } = await import("node:child_process");
const logStream = createWriteStream(logPath, { flags: "a" });
await new Promise<void>((resolve) => {
if (logStream.pending) logStream.once("open", () => resolve());
@@ -90,7 +91,6 @@ async function runDaemon(nerveRoot: string): Promise<void> {
writePidFile(pid);
const { getSocketPath } = await import("../workspace.js");
const ready = await waitForSocket(getSocketPath(), 5000);
if (!ready || !isRunning()) {
+1
View File
@@ -173,6 +173,7 @@ export async function loadComputeFn(senseIndexPath: string): Promise<Result<Comp
let mod: unknown;
try {
// Dynamic import required: user-authored sense module, path resolved at runtime
mod = await import(senseIndexPath);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
+1
View File
@@ -197,6 +197,7 @@ async function loadWorkflowDefinition(
);
}
// Dynamic import required: user-authored workflow module, path resolved at runtime
const mod = await import(indexPath);
const def: unknown = mod.default ?? mod;