@lark-apaas/coding-steering 0.1.12 → 0.1.13-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-steering",
3
- "version": "0.1.12",
3
+ "version": "0.1.13-alpha.1",
4
4
  "description": "Stack-specific steering content for miaoda-coding templates",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: coding-guide
3
- description: "项目全局编码规范,必须在任何代码编写、阅读、修改、排查前加载。覆盖:项目结构、目录组织、文件命名、NestJS 后端(MVCS/Drizzle ORM/API 规范/异常处理/用户上下文)、React 19 前端(shadcn/ui/Tailwind/路由/样式/组件规范)、前后端联调(shared 类型/axiosForBackend)、数据库操作、质量保障流程、日志规范。Use when: 编写任意前端或后端代码、新建页面或模块、修改接口或数据库操作、排查编译错误或运行时问题、代码审查、理解项目架构。"
3
+ description: '项目全局编码规范,必须在任何代码编写、阅读、修改、排查前加载。覆盖:项目结构、目录组织、文件命名、NestJS 后端(MVCS/Drizzle ORM/API 规范/异常处理/用户上下文)、React 19 前端(shadcn/ui/Tailwind/路由/样式/组件规范)、前后端联调(shared 类型/axiosForBackend)、数据库操作、质量保障流程、日志规范。Use when: 编写任意前端或后端代码、新建页面或模块、修改接口或数据库操作、排查编译错误或运行时问题、代码审查、理解项目架构。'
4
4
  ---
5
+
5
6
  # 项目结构
6
7
 
7
8
  ## 根目录组织
@@ -96,7 +97,7 @@ shared/ # 前后端共享的目录
96
97
  ```typescript
97
98
  // ❌ 错误:变量无类型注解 → 回调参数隐式 any → TS7006
98
99
  const items = await this.service.getItems();
99
- items.map(item => item.name); // TS7006: item 隐式具有 any 类型
100
+ items.map((item) => item.name); // TS7006: item 隐式具有 any 类型
100
101
 
101
102
  // ✅ 正确:变量显式类型 + 回调参数显式类型
102
103
  const items: Item[] = await this.service.getItems();
@@ -177,7 +178,6 @@ shared/ # 前后端共享的目录
177
178
  项目有三个**聚合文件**:`server/app.module.ts`、`client/src/app.tsx`、`client/src/api/index.ts`。它们是所有业务模块的汇聚入口,**必须由主 agent 在派发任何业务模块任务之前一次性串行预填**,业务模块任务(可能并行执行)**不得编辑**这三个文件。多方并发编辑同一聚合文件会触发 `old_string` mismatch 和 LLM 兜底重写,显著拖慢生成。
178
179
 
179
180
  **主 agent 预填职责**(规划阶段产出**业务模块清单**后立即执行,任务派发前完成):
180
-
181
181
  - 清单命名约定:目录名 / 路由 / namespace / `api_prefix` 末段统一 `kebab-case`;`module_class` / 页面组件名统一 `PascalCase`;目录名与模块 `name` 一致
182
182
  - **严格串行**编辑以下三个文件(一次一个 tool 调用):
183
183
  - `server/app.module.ts`:在文件顶部追加 `import { UsersModule } from './modules/users/users.module';`,在 `@Module` 的 `imports` 数组里加入 `UsersModule`;`ViewModule` 必须保持 `imports` 数组最后一项(fallback 路由)
@@ -186,7 +186,6 @@ shared/ # 前后端共享的目录
186
186
  - 预填完成后才派发业务模块任务;聚合文件引用的符号(`UsersModule` / `UsersPage` / `./users`)在任务产出前暂时悬空,任务完成后自然对齐
187
187
 
188
188
  **业务模块任务的工作边界**:
189
-
190
189
  - 产出只能落在以下目录之一:`server/modules/<name>/`(Module / Controller / Service / DTO)、`client/src/pages/<name>/`、`client/src/api/<name>/`
191
190
  - **禁止编辑** `server/app.module.ts` / `client/src/app.tsx` / `client/src/api/index.ts`(已由主 agent 预填)
192
191
  - 跨模块依赖(不涉及聚合文件):在源 Module 的 `exports` 中导出即可
@@ -194,8 +193,8 @@ shared/ # 前后端共享的目录
194
193
 
195
194
  ```yaml
196
195
  cross_module_needs:
197
- - type: extra_module # 或 extra_route / extra_provider
198
- reason: "需要 SharedAuthModule 让 UsersController 使用 @Auth"
196
+ - type: extra_module # 或 extra_route / extra_provider
197
+ reason: '需要 SharedAuthModule 让 UsersController 使用 @Auth'
199
198
  module_class: SharedAuthModule
200
199
  module_file: server/modules/shared-auth/shared-auth.module.ts
201
200
  ```
@@ -218,7 +217,7 @@ shared/ # 前后端共享的目录
218
217
  **必须使用注入的 Drizzle 实例**,禁止自建连接:
219
218
 
220
219
  ```typescript
221
- import { DRIZZLE_DATABASE, type PostgresJsDatabase } from "@lark-apaas/fullstack-nestjs-core";
220
+ import { DRIZZLE_DATABASE, type PostgresJsDatabase } from '@lark-apaas/fullstack-nestjs-core';
222
221
 
223
222
  @Injectable()
224
223
  export class TestService {
@@ -234,21 +233,30 @@ export class TestService {
234
233
  // ✅ 条件查询
235
234
  const conditions = [];
236
235
  if (status) conditions.push(eq(users.status, status));
237
- const query = conditions.length > 0
238
- ? db.select().from(users).where(and(...conditions))
239
- : db.select().from(users);
236
+ const query =
237
+ conditions.length > 0
238
+ ? db
239
+ .select()
240
+ .from(users)
241
+ .where(and(...conditions))
242
+ : db.select().from(users);
240
243
  ```
241
244
 
242
245
  - Count 查询:
243
246
 
244
247
  ```typescript
245
248
  // 简单 count
246
- const result = await this.db.select({ count: count() }).from(users).where(eq(users.status, "active"));
249
+ const result = await this.db
250
+ .select({ count: count() })
251
+ .from(users)
252
+ .where(eq(users.status, 'active'));
247
253
  // 子查询 count(关联计数)
248
- const users = await this.db.select({
249
- ...users,
250
- postsCount: this.db.$count(posts, eq(posts.authorId, users.id)),
251
- }).from(users);
254
+ const users = await this.db
255
+ .select({
256
+ ...users,
257
+ postsCount: this.db.$count(posts, eq(posts.authorId, users.id)),
258
+ })
259
+ .from(users);
252
260
  ```
253
261
 
254
262
  ### userProfile 自定义类型
@@ -257,11 +265,11 @@ const users = await this.db.select({
257
265
 
258
266
  ```typescript
259
267
  // schema 示例
260
- export const mockTable = pgTable("mock_table", {
261
- adminUser: userProfile("admin_user"), // custom_type, TS 中为 string
268
+ export const mockTable = pgTable('mock_table', {
269
+ adminUser: userProfile('admin_user'), // custom_type, TS 中为 string
262
270
  });
263
271
  // 使用示例
264
- const userId: string = "user123"; // ✅ 显式注解
272
+ const userId: string = 'user123'; // ✅ 显式注解
265
273
  await db.insert(users).values({ adminUser: userId });
266
274
  await db.select().from(users).where(eq(users.adminUser, userId));
267
275
  ```
@@ -273,7 +281,7 @@ await db.select().from(users).where(eq(users.adminUser, userId));
273
281
  - **UUID 列 `inArray` 必须显式 `::uuid[]`**:标准 `inArray(col, ids)` 会运行时报 `42809: op ANY/ALL (array) requires array on right side`。
274
282
 
275
283
  ```typescript
276
- where: sql`${tasks.id} = ANY(${ids}::uuid[])`
284
+ where: sql`${tasks.id} = ANY(${ids}::uuid[])`;
277
285
  ```
278
286
 
279
287
  - **`customTimestamptz` 跨网络后是 string**:service 内查询返回 `Date`,但 API 响应经 JSON 序列化后前端拿到 ISO string。`shared/api.interface.ts` 中 timestamptz 字段声明为 `string`,前端需要 Date 时手动 `new Date(value)`。
@@ -312,9 +320,9 @@ await db.select().from(users).where(eq(users.adminUser, userId));
312
320
 
313
321
  ## 异常处理
314
322
 
315
- | 分层 | 目标 |
316
- |------|------|
317
- | service | 抛出业务异常 |
323
+ | 分层 | 目标 |
324
+ | ---------- | ------------------------------- |
325
+ | service | 抛出业务异常 |
318
326
  | controller | 不处理异常,交全局 Error Filter |
319
327
 
320
328
  ## 当前用户信息
@@ -336,15 +344,6 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
336
344
  }
337
345
  ```
338
346
 
339
- ## 插件能力(Plugin)
340
-
341
- 本地开发态**暂不支持** PluginInstance / capability 类插件能力的创建与调用 — 这套链路依赖云端 agent 工具(`plugin_instance` / `get_plugin_ai_json`)与平台下发的 `server/capabilities/` 配置,本地没有等价物。
342
-
343
- 如果业务用到飞书多维表格、AI 生文/翻译/分类、消息推送等"插件能力"覆盖的场景:
344
-
345
- - **数据存储/查询类**(多维表格 CRUD、列表分页、聚合统计):优先用本应用自带的 Postgres + Drizzle,数据可在云端运行时再同步飞书 Base
346
- - **AI / 飞书消息类**:在云端发布后由平台插件链路承接;本地开发时可先用接口 mock 桩占位
347
-
348
347
  ## 自动化任务
349
348
 
350
349
  需要设计自动化任务配置与开发时,请调用文档工具召回自动化任务配置与代码编写文档
@@ -371,11 +370,11 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
371
370
 
372
371
  本地 dev 跑起来有**两个端口 + 一个前缀**,都由 `.env.local` 里的 env 控制:
373
372
 
374
- | Env | 进程 / 用途 | 默认 | 沙箱 env-pull 下发? | 备注 |
375
- |---|---|---|---|---|
376
- | `CLIENT_DEV_PORT` | vite / rspack dev server | `8080` | ❌ 不下发 | **唯一入口**:serve 前端 + 反代 `/api/*` 给 NestJS + 注入 `x-larkgw-suda-webuser` 模拟登录态。端口被占就 Agent 自己改 `.env.local` |
377
- | `SERVER_PORT` | NestJS | `3000` | ❌ 不下发 | 仅 vite/rspack 反代用,不直接给 agent / curl 用。端口被占就 Agent 自己改 `.env.local` |
378
- | `CLIENT_BASE_PATH` | 前端路由 base + 后端 routes.json serve 路径 | `/` | ✅ `/app/<app_id>` | 浏览器 URL 前缀;agent / curl 调 `/api/*` 也必须带这个前缀 |
373
+ | Env | 进程 / 用途 | 默认 | 沙箱 env-pull 下发? | 备注 |
374
+ | ------------------ | ------------------------------------------- | ------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
375
+ | `CLIENT_DEV_PORT` | vite / rspack dev server | `8080` | ❌ 不下发 | **唯一入口**:serve 前端 + 反代 `/api/*` 给 NestJS + 注入 `x-larkgw-suda-webuser` 模拟登录态。端口被占就 Agent 自己改 `.env.local` |
376
+ | `SERVER_PORT` | NestJS | `3000` | ❌ 不下发 | 仅 vite/rspack 反代用,不直接给 agent / curl 用。端口被占就 Agent 自己改 `.env.local` |
377
+ | `CLIENT_BASE_PATH` | 前端路由 base + 后端 routes.json serve 路径 | `/` | ✅ `/app/<app_id>` | 浏览器 URL 前缀;agent / curl 调 `/api/*` 也必须带这个前缀 |
379
378
 
380
379
  完整入口形态 = `http://localhost:<CLIENT_DEV_PORT><CLIENT_BASE_PATH>/...`。启动日志里
381
380
  vite/rspack 打印的 `Local: http://localhost:<port>/...` 才是入口,具体值以 `.env.local`
@@ -413,13 +412,13 @@ NestJS 自己不读 env。直连 NestJS 端口 → header 缺失 → `req.userCo
413
412
  和 header,值字面相等才放行。任意非空相等值都行(注意打**client dev port**,不是
414
413
  NestJS 端口):
415
414
 
416
- ```bash
417
- # <PORT> = .env.local 里的 CLIENT_DEV_PORT(常见 8080 / 8001)
418
- # <BASE_PATH> = .env.local 里的 CLIENT_BASE_PATH(沙箱下发为 /app/<app_id>,本地裸跑可能为空)
419
- curl -H 'Cookie: suda-csrf-token=x' \
420
- -H 'X-Suda-Csrf-Token: x' \
421
- http://localhost:<PORT><BASE_PATH>/api/notes
422
- ```
415
+ ```bash
416
+ # <PORT> = .env.local 里的 CLIENT_DEV_PORT(常见 8080 / 8001)
417
+ # <BASE_PATH> = .env.local 里的 CLIENT_BASE_PATH(沙箱下发为 /app/<app_id>,本地裸跑可能为空)
418
+ curl -H 'Cookie: suda-csrf-token=x' \
419
+ -H 'X-Suda-Csrf-Token: x' \
420
+ http://localhost:<PORT><BASE_PATH>/api/notes
421
+ ```
423
422
 
424
423
  **不要**因为撞 403 就去改后端 csrf 中间件或者怀疑业务逻辑 —— csrf 保护是有意的,
425
424
  业务本身没问题。
@@ -480,20 +479,20 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
480
479
 
481
480
  ## 官方内置组件
482
481
 
483
- | 组件 | 来源 | 用途 |
484
- |------|------|------|
485
- | Table | `@lark-apaas/client-toolkit/antd-table` | 数据表格,**先调 `/table-skill`** |
486
- | UserSelect/UserDisplay/UserProfile/DepartmentSelect | `business-ui/*` | 用户/部门选择展示 |
487
- | TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
488
- | Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
482
+ | 组件 | 来源 | 用途 |
483
+ | --------------------------------------------------- | --------------------------------------- | --------------------------------- |
484
+ | Table | `@lark-apaas/client-toolkit/antd-table` | 数据表格,**先调 `/table-skill`** |
485
+ | UserSelect/UserDisplay/UserProfile/DepartmentSelect | `business-ui/*` | 用户/部门选择展示 |
486
+ | TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
487
+ | Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
489
488
 
490
489
  ### 组件 Skill 召回规则(强制执行)
491
490
 
492
- | 场景 | Skill | 说明 |
493
- |------|-------|------|
494
- | 表单、Form、Zod | `/forms-skill` | Shadcn Form + React Hook Form + Zod |
495
- | 图表、Chart、ECharts | `/charts-skill` | shadcn/ui + ReactECharts |
496
- | 表格、Table | `/table-skill` | antd-table |
491
+ | 场景 | Skill | 说明 |
492
+ | -------------------- | --------------- | ----------------------------------- |
493
+ | 表单、Form、Zod | `/forms-skill` | Shadcn Form + React Hook Form + Zod |
494
+ | 图表、Chart、ECharts | `/charts-skill` | shadcn/ui + ReactECharts |
495
+ | 表格、Table | `/table-skill` | antd-table |
497
496
 
498
497
  禁止未调用 Skill 直接编写表单/图表/表格代码。
499
498
 
@@ -511,15 +510,15 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
511
510
 
512
511
  **零假设原则**:绝不基于假设使用任何子模块。使用任何 `@lark-apaas/client-toolkit` 子模块前**必须**:① 先查询本地已加载的 Skills 或对应包文档 → ② 严格按文档编码。**禁止直接调用 `@lark-apaas/client-toolkit` 任何函数/方法**(不基于查询到的文档)。该库**仅可在前端代码中使用,禁止在后端代码中引用**。
513
512
 
514
- | 功能 | 导入路径 |
515
- |------|----------|
516
- | 插件调用(Client) | `import { capabilityClient } from "@lark-apaas/client-toolkit"` |
517
- | 日志 | `import { logger } from "@lark-apaas/client-toolkit/logger"` |
518
- | 文件上传下载 | `import { getDataloom } from "@lark-apaas/client-toolkit/dataloom"` |
519
- | 应用信息 | `import { useAppInfo } from "@lark-apaas/client-toolkit/hooks/useAppInfo"` |
520
- | 当前用户 | `import { useCurrentUserProfile } from "@lark-apaas/client-toolkit/hooks/useCurrentUserProfile"` |
521
- | URL 解析 | `import { resolveAppUrl } from "@lark-apaas/client-toolkit/utils/resolveAppUrl"` |
522
- | 批量用户信息 | `import { UserService } from "@lark-apaas/client-toolkit/tools/services"` |
513
+ | 功能 | 导入路径 |
514
+ | ------------------ | ------------------------------------------------------------------------------------------------ |
515
+ | 插件调用(Client) | `import { capabilityClient } from "@lark-apaas/client-toolkit"` |
516
+ | 日志 | `import { logger } from "@lark-apaas/client-toolkit/logger"` |
517
+ | 文件上传下载 | `import { getDataloom } from "@lark-apaas/client-toolkit/dataloom"` |
518
+ | 应用信息 | `import { useAppInfo } from "@lark-apaas/client-toolkit/hooks/useAppInfo"` |
519
+ | 当前用户 | `import { useCurrentUserProfile } from "@lark-apaas/client-toolkit/hooks/useCurrentUserProfile"` |
520
+ | URL 解析 | `import { resolveAppUrl } from "@lark-apaas/client-toolkit/utils/resolveAppUrl"` |
521
+ | 批量用户信息 | `import { UserService } from "@lark-apaas/client-toolkit/tools/services"` |
523
522
 
524
523
  **useCurrentUserProfile** 返回 `Partial<IUserProfile>`,初始为空对象:访问字段用 `?.`,判断加载完成用 `if (!userInfo?.user_id)`。
525
524
 
@@ -569,9 +568,9 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
569
568
 
570
569
  ```typescript
571
570
  import { resolveAppUrl } from '@lark-apaas/client-toolkit/utils/resolveAppUrl';
572
- const shareUrl = resolveAppUrl(`/detail/${id}`); // ✅ 路由路径→完整 URL
571
+ const shareUrl = resolveAppUrl(`/detail/${id}`); // ✅ 路由路径→完整 URL
573
572
  const fixedUrl = resolveAppUrl(`${origin}/detail/${id}`); // ✅ 自动修正
574
- resolveAppUrl('https://other-site.com/page'); // ✅ 外部链接原样返回
573
+ resolveAppUrl('https://other-site.com/page'); // ✅ 外部链接原样返回
575
574
  // ❌ 严禁自行拼接:`${window.location.origin}/detail/${id}`
576
575
  ```
577
576
 
@@ -605,11 +604,11 @@ return <h1>{data?.title || '未知标题'}</h1>;
605
604
 
606
605
  为特定功能的顶层容器添加 `data-ai-section-type` 属性:
607
606
 
608
- | 值 | 场景 |
609
- |----|------|
610
- | `card-stat` | 横向指标卡容器 |
611
- | `card-list` | Card 组件的 flex 列表 |
612
- | `button` | 任意 Button |
607
+ | 值 | 场景 |
608
+ | ----------- | ------------------------- |
609
+ | `card-stat` | 横向指标卡容器 |
610
+ | `card-list` | Card 组件的 flex 列表 |
611
+ | `button` | 任意 Button |
613
612
  | `card-menu` | Card 组件的 grid 网格菜单 |
614
613
 
615
614
  ## 图片规范
@@ -628,23 +627,23 @@ return <h1>{data?.title || '未知标题'}</h1>;
628
627
 
629
628
  ## 核心功能依赖
630
629
 
631
- | 类别 | 库 |
632
- |------|-----|
633
- | 动画 | Framer Motion, GSAP |
634
- | 日期 | dayjs |
635
- | 日期选择器 | shadcn Calendar + Popover(禁止 input[type="date"]) |
636
- | 验证 | zod |
637
- | 工具函数 | lodash |
638
- | 样式 | clsx |
639
- | Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
640
- | PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
641
- | 文件上传 | react-dropzone |
642
- | 二维码 | qrcode.react |
643
- | 用户反馈 | sonner |
644
- | 拖拽 | @dnd-kit/core |
645
- | 数字动画 | react-countup |
646
- | Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
647
- | 3D 场景 | cobe |
630
+ | 类别 | 库 |
631
+ | ---------- | ------------------------------------------------------------------------ |
632
+ | 动画 | Framer Motion, GSAP |
633
+ | 日期 | dayjs |
634
+ | 日期选择器 | shadcn Calendar + Popover(禁止 input[type="date"]) |
635
+ | 验证 | zod |
636
+ | 工具函数 | lodash |
637
+ | 样式 | clsx |
638
+ | Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
639
+ | PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
640
+ | 文件上传 | react-dropzone |
641
+ | 二维码 | qrcode.react |
642
+ | 用户反馈 | sonner |
643
+ | 拖拽 | @dnd-kit/core |
644
+ | 数字动画 | react-countup |
645
+ | Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
646
+ | 3D 场景 | cobe |
648
647
 
649
648
  ## 滚动分页最佳实践
650
649