RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/xin-lai-codespirit-agents ↔ xin-lai-codespirit-cursor-rules-js

Comparison

A · AGENTS.md · xin-lai/CodeSpiritB · Cursor rules · xin-lai/CodeSpirit
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections11475%
Commands0200%
Section tags12125%

What each file covers

Sections

1 shared · 14 only in A · 7 only in B
  • − Copilot instructions
  • − General recommendations for working with Aspire
  • − Running the application
  • − Checking resources
  • − Listing integrations
  • − Debugging issues
  • − Other Aspire MCP tools
  • − Playwright MCP server
  • − Updating the app host
  • − Persistent containers
  • − Aspire workload
  • − Official documentation
  • − BMAD AI 工作流
  • − 与 CodeSpirit 规范集成
  • + JavaScript 开发规范
  • + IIFE 包装
  • + 函数注释(JSDoc)
  • + AMIS 官方文档
  • + Token 管理
  • + TokenManager 使用
  • + API 管理器模式
  •   获取帮助

Commands

0 shared · 2 only in A · 0 only in B
  • − dotnet tool install --global dotnet-outdated-tool
  • − dotnet-outdated

Section tags

1 shared · 2 only in A · 1 only in B
  • − code-style
  • − agent-behaviour
  • + api
  •   docs

Line diff

+535 added−68 removed34 unchanged6.0% identical
xin-lai/CodeSpirit · AGENTS.md
@@ −1 @@
1# Copilot instructions
 
 
 
 
2 
3This repository is set up to use Aspire. Aspire is an orchestrator for the entire application and will take care of configuring dependencies, building, and running the application. The resources that make up the application are defined in `apphost.cs` including application code and external dependencies.
4 
5## General recommendations for working with Aspire
61. Before making any changes always run the apphost using `aspire run` and inspect the state of resources to make sure you are building from a known state.
71. Changes to the _apphost.cs_ file will require a restart of the application to take effect.
82. Make changes incrementally and run the aspire application using the `aspire run` command to validate changes.
93. Use the Aspire MCP tools to check the status of resources and debug issues.
10 
11## Running the application
12To run the application run the following command:
13 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14```
15aspire run
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16```
17 
18If there is already an instance of the application running it will prompt to stop the existing instance. You only need to restart the application if code in `apphost.cs` is changed, but if you experience problems it can be useful to reset everything to the starting state.
19 
20## Checking resources
21To check the status of resources defined in the app model use the _list resources_ tool. This will show you the current state of each resource and if there are any issues. If a resource is not running as expected you can use the _execute resource command_ tool to restart it or perform other actions.
22 
23## Listing integrations
24IMPORTANT! When a user asks you to add a resource to the app model you should first use the _list integrations_ tool to get a list of the current versions of all the available integrations. You should try to use the version of the integration which aligns with the version of the Aspire.AppHost.Sdk. Some integration versions may have a preview suffix. Once you have identified the correct integration you should always use the _get integration docs_ tool to fetch the latest documentation for the integration and follow the links to get additional guidance.
 
 
 
 
 
 
 
25 
26## Debugging issues
27IMPORTANT! Aspire is designed to capture rich logs and telemetry for all resources defined in the app model. Use the following diagnostic tools when debugging issues with the application before making changes to make sure you are focusing on the right things.
28 
291. _list structured logs_; use this tool to get details about structured logs.
302. _list console logs_; use this tool to get details about console logs.
313. _list traces_; use this tool to get details about traces.
324. _list trace structured logs_; use this tool to get logs related to a trace
 
 
 
 
 
 
 
 
 
 
33 
34## Other Aspire MCP tools
 
 
 
 
 
 
 
 
 
 
 
35 
361. _select apphost_; use this tool if working with multiple app hosts within a workspace.
372. _list apphosts_; use this tool to get details about active app hosts.
38 
39## Playwright MCP server
40 
41The playwright MCP server has also been configured in this repository and you should use it to perform functional investigations of the resources defined in the app model as you work on the codebase. To get endpoints that can be used for navigation using the playwright MCP server use the list resources tool.
42 
43## Updating the app host
44The user may request that you update the Aspire apphost. You can do this using the `aspire update` command. This will update the apphost to the latest version and some of the Aspire specific packages in referenced projects, however you may need to manually update other packages in the solution to ensure compatibility. You can consider using the `dotnet-outdated` with the users consent. To install the `dotnet-outdated` tool use the following command:
 
 
 
 
 
 
 
 
 
 
45 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46```
47dotnet tool install --global dotnet-outdated-tool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48```
49 
50## Persistent containers
51IMPORTANT! Consider avoiding persistent containers early during development to avoid creating state management issues when restarting the app.
52 
53## Aspire workload
54IMPORTANT! The aspire workload is obsolete. You should never attempt to install or use the Aspire workload.
55 
56## Official documentation
57IMPORTANT! Always prefer official documentation when available. The following sites contain the official documentation for Aspire and related components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58 
591. https://aspire.dev
602. https://learn.microsoft.com/dotnet/aspire
613. https://nuget.org (for specific integration package details)
62 
63## BMAD AI 工作流
64 
65本项目已集成 BMAD (Breakthrough Method of Agile AI-Driven Development) 完整工作流,用于结构化的软件开发生命周期管理。
66 
67### 快速开始
 
 
 
 
68 
691. **小型任务/Bug 修复** (Quick Flow):
70 - `/quick-spec` - 创建技术规范
71 - `/quick-dev` - 实现变更
72 - `/code-review` - 代码审查
 
 
 
73 
742. **完整功能开发** (Full Flow):
75 - `/product-brief` - 产品需求简报
76 - `/create-prd` - 创建 PRD
77 - `/create-architecture` - 架构设计
78 - `/create-epics-and-stories` - 拆分为 Epic 和 Story
79 - `/sprint-planning` - Sprint 规划
80 - `/dev-story` - 实现 Story
81 - `/code-review` - 代码审查
82 - `/retrospective` - 复盘
83 
84### 与 CodeSpirit 规范集成
85 
86BMAD 工作流已配置为自动遵循 CodeSpirit 的所有开发规范(位于 `.cursor/rules/`)。在使用 BMAD 时:
87 
88- PRD 会自动考虑多租户、多数据库、AI 功能等项目特性
89- 架构设计会遵循依赖注入、缓存策略等规范
90- Story 实现会应用正确的命名约定、DTO 设计、控制器规范等
91- 代码审查会执行 CodeSpirit 特定的审查清单
 
 
 
 
92 
93### 获取帮助
94 
95任何时候,输入 `/bmad-help` 可获取上下文相关的指导。
96 
97详细使用指南请参考:
98- **[BMAD 使用教程](Docs/bmad/bmad-tutorial.md)** - 完整的综合教程(推荐新手阅读)
99- [BMAD 工作流指南](Docs/bmad/bmad-workflow-guide.md) - 详细的工作流使用指南
100- [BMAD 团队培训指南](Docs/bmad/bmad-team-guide.md) - 团队培训材料
101- [BMAD 集成技能](.cursor/skills/bmad-integration/SKILL.md) - BMAD 与 CodeSpirit 集成
102- [项目上下文文档](project-context.md) - 项目上下文和规范引用
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
xin-lai/CodeSpirit · .cursor/rules/js.mdc
@@ +1 @@
1---
2description: CodeSpirit JavaScript 开发规范 - AMIS集成、模块模式、API请求、Token管理
3globs: *.js
4alwaysApply: false
5---
6 
7# JavaScript 开发规范
8 
9## 模块模式
 
 
 
 
10 
11### IIFE 包装
 
12 
13所有 JS 文件使用立即调用函数表达式(IIFE)包装,启用严格模式:
14 
15```javascript
16/**
17 * 模块说明
18 * @module ModuleName
19 */
20(function() {
21 'use strict';
22
23 // 模块代码
24
25 // 导出到全局
26 window.ModuleName = {
27 // 公共 API
28 };
29})();
30```
31 
32### 命名空间导出
33 
34全局对象使用 `window` 命名空间导出:
35 
36```javascript
37// ✅ 正确:使用 window 命名空间
38window.TokenManager = (function() {
39 'use strict';
40
41 function getToken() { /* ... */ }
42
43 return {
44 getToken,
45 setToken,
46 clearToken
47 };
48})();
49 
50// ✅ 正确:使用 CodeSpirit 命名空间
51window.CodeSpirit = window.CodeSpirit || {};
52window.CodeSpirit.i18n = {
53 t: function(key, params) { /* ... */ }
54};
55 
56// ✅ 正确:ES6 类导出
57class NotificationClient {
58 constructor(hubUrl = '/notification-hub') {
59 this.hubUrl = hubUrl;
60 }
61}
62window.NotificationClient = NotificationClient;
63```
64 
65## 文档注释规范
66 
67### 文件头注释
 
68 
69```javascript
70/**
71 * 考试系统API请求管理器
72 * 负责处理API地址转换和统一的请求处理
73 * @module ExamApiManager
74 * @version 2.0.0
75 * @author CodeSpirit Team
76 */
77```
78 
79### 函数注释(JSDoc)
 
80 
81```javascript
82/**
83 * 设置认证token
84 * @param {string} token - 访问token
85 * @param {number} [expiryInHours=24] - 过期时间(小时)
86 * @returns {void}
87 * @throws {Error} 当token为空时抛出错误
88 */
89function setToken(token, expiryInHours = 24) {
90 if (!token || typeof token !== 'string') {
91 throw new Error('Token must be a non-empty string');
92 }
93 // ...
94}
95 
96/**
97 * 统一的API请求函数
98 * @param {string} url - API路径
99 * @param {Object} [options={}] - fetch选项
100 * @returns {Promise<Object>} API响应数据
101 * @example
102 * const data = await ExamApiManager.request('/exam/api/questions', { method: 'GET' });
103 */
104async function request(url, options = {}) {
105 // ...
106}
107```
108 
109## AMIS 框架集成
 
110 
111### 主题配置
112 
113项目使用 **antd** 主题 [[memory:8912919]]:
114 
115```javascript
116// 初始化 AMIS
117let amisScoped = amis.embed('#root', amisJSON, {
118 location: history.location,
119 data: {},
120 context: {
121 WEB_HOST: webHost
122 }
123}, {
124 theme: 'antd' // 必须使用 antd 主题
125});
126```
127 
128### 事件系统
129 
130使用 `onEvent` 配置事件监听:
131 
132```javascript
133{
134 type: 'form',
135 api: '/identity/api/identity/auth/login',
136 onEvent: {
137 // 表单提交成功事件
138 submitSucc: {
139 actions: [
140 {
141 actionType: 'custom',
142 script: `
143 const token = event.data.token;
144 TokenManager.setToken(token, 24);
145 window.location.href = '/';
146 `
147 }
148 ]
149 },
150 // 数据初始化完成事件
151 fetchInited: {
152 actions: [
153 {
154 actionType: 'custom',
155 script: 'window.fetchUnreadNotificationCount();'
156 }
157 ]
158 }
159 }
160}
161```
162 
163### 行为类型
164 
165优先使用 AMIS 内置行为类型(actionType):
166 
167```javascript
168// ✅ 核心行为
169{ actionType: 'ajax', api: 'POST:/api/submit' }
170{ actionType: 'link', link: '/dashboard' }
171{ actionType: 'dialog', dialog: { /* ... */ } }
172{ actionType: 'reload', target: 'crud' }
173{ actionType: 'copy', content: '${text}' }
174 
175// ✅ 表单行为
176{ actionType: 'submit' }
177{ actionType: 'reset' }
178{ actionType: 'clear' }
179 
180// ✅ 自定义脚本(仅在必要时使用)
181{
182 actionType: 'custom',
183 script: `
184 const tenantId = event.data.tenantId;
185 window.location.href = '/' + tenantId + '/login';
186 `
187}
188```
189 
190### 请求适配器
 
191 
192使用 `requestAdaptor` 和 `adaptor` 处理请求和响应:
 
193 
194```javascript
195api: {
196 method: 'post',
197 url: '/identity/api/identity/auth/login',
198
199 // 请求适配器 - 添加认证头
200 requestAdaptor: function(api) {
201 const token = TokenManager.getToken();
202 api.headers = api.headers || {};
203 api.headers['Authorization'] = token ? 'Bearer ' + token : '';
204 api.headers['X-Forwarded-With'] = 'CodeSpirit';
205 api.headers['X-Tenant-Id'] = window.tenantId || 'system';
206 return api;
207 },
208
209 // 响应适配器 - 处理响应数据
210 adaptor: function(payload, response, api) {
211 if (response.status === 401) {
212 window.location.href = '/login';
213 return { msg: '登录过期!' };
214 }
215
216 if (payload.status === 0 && payload.data) {
217 TokenManager.setToken(payload.data.token, 24);
218 }
219
220 return payload;
221 }
222}
223```
224 
225## Token 管理
 
 
226 
227### TokenManager 使用
228 
229使用 `TokenManager` 统一管理认证状态:
230 
231```javascript
232// 初始化模式
233TokenManager.initSystemMode(); // 系统平台
234TokenManager.initTenantMode('tenant-id'); // 租户平台
235TokenManager.initClientMode('tenant-id', 'exam'); // 客户端平台
236 
237// Token 操作
238TokenManager.setToken('access-token', 24); // 设置 token(24小时过期)
239const token = TokenManager.getToken(); // 获取 token
240TokenManager.clearToken(); // 清除 token
241TokenManager.hasToken(); // 检查是否有 token
242TokenManager.isTokenExpired(); // 检查是否过期
243TokenManager.isAuthenticated(); // 检查是否已认证
244 
245// 扩展功能
246TokenManager.setTokenExtended(accessToken, refreshToken, expiresIn, tenantId);
247TokenManager.getRefreshToken();
248TokenManager.getAuthHeaders(); // 获取认证请求头
249TokenManager.setUserInfo(userInfo);
250TokenManager.getUserInfo();
251```
 
 
252 
253### 认证请求头
254 
255所有 API 请求必须携带认证头:
256 
257```javascript
258const headers = {
259 'Authorization': token ? 'Bearer ' + token : '',
260 'X-Forwarded-With': 'CodeSpirit',
261 'X-Tenant-Id': tenantId || 'system',
262 'Content-Type': 'application/json'
263};
264```
265 
266## API 请求规范
267 
268### 服务发现路径
269 
270API 路径必须附带服务短名:
271 
272```javascript
273// ✅ 正确:附带服务名
274'/identity/api/identity/profile'
275'/exam/api/exam/questions'
276'/messaging/api/messaging/messages/my/list'
277'/survey/api/surveys/${surveyId}'
278 
279// ❌ 错误:缺少服务名
280'/api/identity/profile'
281'/api/questions'
282```
283 
284### API 管理器模式
285 
286使用统一的 API 管理器处理请求:
287 
288```javascript
289/**
290 * API管理器
291 */
292window.ExamApiManager = {
293 /**
294 * 统一的API请求函数
295 * @param {string} url - API路径
296 * @param {Object} options - fetch选项
297 * @returns {Promise} API响应数据
298 */
299 request: async function(url, options = {}) {
300 const token = window.TokenManager?.getToken();
301
302 const requestConfig = {
303 ...options,
304 headers: {
305 'Authorization': token ? 'Bearer ' + token : '',
306 'X-Tenant-Id': window.tenantId,
307 'X-Forwarded-With': 'CodeSpirit',
308 'Content-Type': 'application/json',
309 ...options.headers
310 }
311 };
312 
313 const response = await fetch(url, requestConfig);
314
315 // 处理认证失败
316 if (response.status === 401) {
317 window.location.href = '/login';
318 throw new Error('认证失败,请重新登录');
319 }
320
321 if (!response.ok) {
322 throw new Error(`HTTP ${response.status}: ${response.statusText}`);
323 }
324
325 const result = await response.json();
326
327 if (result.status !== undefined && result.status !== 0) {
328 throw new Error(result.msg || '请求失败');
329 }
330
331 return result.data || result;
332 },
333 
334 get: function(url, options = {}) {
335 return this.request(url, { ...options, method: 'GET' });
336 },
337 
338 post: function(url, data = null, options = {}) {
339 return this.request(url, {
340 ...options,
341 method: 'POST',
342 body: data ? JSON.stringify(data) : undefined
343 });
344 }
345};
346```
347 
348## 缓存管理
349 
350### 缓存键命名
351 
352使用租户隔离的缓存键:
353 
354```javascript
355// 格式:{module}_cache_{tenantId}_{key}
356const cacheKey = `exam_cache_${tenantId}_login_config`;
357const cacheKey = `survey_cache_${tenantId}_form_data`;
358```
359 
360### 缓存工具类
361 
362```javascript
363const CacheManager = {
364 /**
365 * 获取缓存数据
366 * @param {string} key - 缓存key
367 * @param {string} tenantId - 租户ID
368 * @returns {Object|null} 缓存的数据
369 */
370 get: function(key, tenantId) {
371 try {
372 const cacheKey = `exam_cache_${tenantId}_${key}`;
373 const cached = sessionStorage.getItem(cacheKey);
374 if (!cached) return null;
375 
376 const data = JSON.parse(cached);
377
378 // 检查是否过期
379 if (data.expiry && Date.now() > data.expiry) {
380 sessionStorage.removeItem(cacheKey);
381 return null;
382 }
383
384 return data.value;
385 } catch (error) {
386 console.error('[缓存读取失败]', error);
387 return null;
388 }
389 },
390 
391 /**
392 * 设置缓存数据
393 * @param {string} key - 缓存key
394 * @param {string} tenantId - 租户ID
395 * @param {Object} value - 要缓存的数据
396 * @param {number} ttl - 过期时间(毫秒)
397 */
398 set: function(key, tenantId, value, ttl = 30 * 60 * 1000) {
399 try {
400 const cacheKey = `exam_cache_${tenantId}_${key}`;
401 const data = {
402 value: value,
403 expiry: ttl ? Date.now() + ttl : null
404 };
405 sessionStorage.setItem(cacheKey, JSON.stringify(data));
406 } catch (error) {
407 console.error('[缓存写入失败]', error);
408 }
409 }
410};
411```
412 
413## 国际化
414 
415使用 `CodeSpirit.i18n` 进行翻译:
416 
417```javascript
418// 初始化(服务器端调用)
419window.CodeSpirit.i18n.init('zh-CN', {
420 'Login.Title': '用户登录',
421 'Login.Success': '登录成功,欢迎 {userName}!'
422});
423 
424// 获取翻译文本
425const title = CodeSpirit.i18n.t('Login.Title');
426const message = CodeSpirit.i18n.t('Login.Success', { userName: 'John' });
427 
428// 切换语言
429CodeSpirit.i18n.switchLanguage('en');
430```
431 
432## 类定义规范
433 
434使用 ES6 class 语法:
435 
436```javascript
437/**
438 * 通知客户端
439 * 提供与通知服务的连接和消息处理功能
440 */
441class NotificationClient {
442 /**
443 * @param {string} hubUrl - SignalR Hub URL
444 */
445 constructor(hubUrl = '/notification-hub') {
446 this.hubUrl = hubUrl;
447 this.connection = null;
448 this.handlers = new Map();
449 }
450 
451 /**
452 * 连接到通知服务
453 * @returns {Promise} 连接Promise
454 */
455 async connect() {
456 this.connection = new signalR.HubConnectionBuilder()
457 .withUrl(this.hubUrl)
458 .withAutomaticReconnect()
459 .build();
460 
461 await this.connection.start();
462 console.log('通知连接已建立');
463 }
464 
465 /**
466 * 注册消息处理器
467 * @param {string} topic - 主题名称
468 * @param {string} type - 消息类型
469 * @param {Function} handler - 处理函数
470 */
471 on(topic, type, handler) {
472 const key = `${topic}:${type}`;
473 if (!this.handlers.has(key)) {
474 this.handlers.set(key, []);
475 }
476 this.handlers.get(key).push(handler);
477 }
478}
479 
480window.NotificationClient = NotificationClient;
481```
482 
483## 代码质量要求
484 
485### 函数长度限制
486 
487一个函数不超过 30 行代码,复杂逻辑拆分为多个小函数:
488 
489```javascript
490// ✅ 正确:拆分为多个小函数
491function handleLoginSuccess(payload) {
492 saveToken(payload.data.token);
493 redirectToTarget();
494}
495 
496function saveToken(token) {
497 TokenManager.setToken(token, 24);
498}
499 
500function redirectToTarget() {
501 const redirectUrl = getRedirectUrl();
502 if (isValidRedirect(redirectUrl)) {
503 window.location.href = redirectUrl;
504 } else {
505 window.location.href = '/';
506 }
507}
508```
509 
510### 样式分离
511 
512样式写入独立的 CSS 文件,不在 JS 中内联样式:
513 
514```javascript
515// ✅ 正确:使用 CSS 类名
516element.className = 'survey-container loading';
517 
518// ❌ 避免:内联样式
519element.style.backgroundColor = '#fff';
520element.style.padding = '20px';
521```
522 
523### 响应式支持
524 
525界面需要支持响应式布局和移动端适配:
526 
527```javascript
528// AMIS 配置中使用响应式断点
529{
530 type: 'grid',
531 columns: [
532 {
533 xs: 12, // 手机:全宽
534 sm: 6, // 平板:半宽
535 md: 4, // 桌面:三分之一
536 lg: 3, // 大屏:四分之一
537 body: { /* ... */ }
538 }
539 ]
540}
541```
542 
543## 禁止事项
544 
545```javascript
546// ❌ 禁止:自定义 DOM 事件(使用 AMIS 事件系统)
547document.getElementById('btn').addEventListener('click', handler);
548 
549// ❌ 禁止:直接操作 DOM(除非必要)
550document.getElementById('content').innerHTML = html;
551 
552// ❌ 禁止:使用 var 声明变量
553var token = getToken();
554 
555// ❌ 禁止:缺少错误处理
556const response = await fetch(url);
557const data = await response.json();
558 
559// ❌ 禁止:硬编码敏感信息
560const apiKey = 'sk-xxxxxxxx';
561```
562 
563## AMIS 官方文档
564 
565- 📖 组件文档:https://aisuda.bce.baidu.com/amis/zh-CN/docs/index
566- 🎨 事件系统:https://aisuda.bce.baidu.com/amis/zh-CN/components/action#%E4%BA%8B%E4%BB%B6%E8%A1%A8
567- 🔘 行为按钮:https://aisuda.bce.baidu.com/amis/zh-CN/components/action
568- 📝 表单组件:https://aisuda.bce.baidu.com/amis/zh-CN/components/form/index
569 
@@ −1 +1 @@
1−# Copilot instructions
1+---
2+description: CodeSpirit JavaScript 开发规范 - AMIS集成、模块模式、API请求、Token管理
3+globs: *.js
4+alwaysApply: false
5+---
26  
3−This repository is set up to use Aspire. Aspire is an orchestrator for the entire application and will take care of configuring dependencies, building, and running the application. The resources that make up the application are defined in `apphost.cs` including application code and external dependencies.
7+# JavaScript 开发规范
48  
5−## General recommendations for working with Aspire
6−1. Before making any changes always run the apphost using `aspire run` and inspect the state of resources to make sure you are building from a known state.
7−1. Changes to the _apphost.cs_ file will require a restart of the application to take effect.
8−2. Make changes incrementally and run the aspire application using the `aspire run` command to validate changes.
9−3. Use the Aspire MCP tools to check the status of resources and debug issues.
9+## 模块模式
1010  
11−## Running the application
12−To run the application run the following command:
11+### IIFE 包装
1312  
13+所有 JS 文件使用立即调用函数表达式(IIFE)包装,启用严格模式:
14+ 
15+```javascript
16+/**
17+ * 模块说明
18+ * @module ModuleName
19+ */
20+(function() {
21+ 'use strict';
22+
23+ // 模块代码
24+
25+ // 导出到全局
26+ window.ModuleName = {
27+ // 公共 API
28+ };
29+})();
1430 ```
15−aspire run
31+ 
32+### 命名空间导出
33+ 
34+全局对象使用 `window` 命名空间导出:
35+ 
36+```javascript
37+// ✅ 正确:使用 window 命名空间
38+window.TokenManager = (function() {
39+ 'use strict';
40+
41+ function getToken() { /* ... */ }
42+
43+ return {
44+ getToken,
45+ setToken,
46+ clearToken
47+ };
48+})();
49+ 
50+// ✅ 正确:使用 CodeSpirit 命名空间
51+window.CodeSpirit = window.CodeSpirit || {};
52+window.CodeSpirit.i18n = {
53+ t: function(key, params) { /* ... */ }
54+};
55+ 
56+// ✅ 正确:ES6 类导出
57+class NotificationClient {
58+ constructor(hubUrl = '/notification-hub') {
59+ this.hubUrl = hubUrl;
60+ }
61+}
62+window.NotificationClient = NotificationClient;
1663 ```
1764  
18−If there is already an instance of the application running it will prompt to stop the existing instance. You only need to restart the application if code in `apphost.cs` is changed, but if you experience problems it can be useful to reset everything to the starting state.
65+## 文档注释规范
1966  
20−## Checking resources
21−To check the status of resources defined in the app model use the _list resources_ tool. This will show you the current state of each resource and if there are any issues. If a resource is not running as expected you can use the _execute resource command_ tool to restart it or perform other actions.
67+### 文件头注释
2268  
23−## Listing integrations
24−IMPORTANT! When a user asks you to add a resource to the app model you should first use the _list integrations_ tool to get a list of the current versions of all the available integrations. You should try to use the version of the integration which aligns with the version of the Aspire.AppHost.Sdk. Some integration versions may have a preview suffix. Once you have identified the correct integration you should always use the _get integration docs_ tool to fetch the latest documentation for the integration and follow the links to get additional guidance.
69+```javascript
70+/**
71+ * 考试系统API请求管理器
72+ * 负责处理API地址转换和统一的请求处理
73+ * @module ExamApiManager
74+ * @version 2.0.0
75+ * @author CodeSpirit Team
76+ */
77+```
2578  
26−## Debugging issues
27−IMPORTANT! Aspire is designed to capture rich logs and telemetry for all resources defined in the app model. Use the following diagnostic tools when debugging issues with the application before making changes to make sure you are focusing on the right things.
79+### 函数注释(JSDoc)
2880  
29−1. _list structured logs_; use this tool to get details about structured logs.
30−2. _list console logs_; use this tool to get details about console logs.
31−3. _list traces_; use this tool to get details about traces.
32−4. _list trace structured logs_; use this tool to get logs related to a trace
81+```javascript
82+/**
83+ * 设置认证token
84+ * @param {string} token - 访问token
85+ * @param {number} [expiryInHours=24] - 过期时间(小时)
86+ * @returns {void}
87+ * @throws {Error} 当token为空时抛出错误
88+ */
89+function setToken(token, expiryInHours = 24) {
90+ if (!token || typeof token !== 'string') {
91+ throw new Error('Token must be a non-empty string');
92+ }
93+ // ...
94+}
3395  
34−## Other Aspire MCP tools
96+/**
97+ * 统一的API请求函数
98+ * @param {string} url - API路径
99+ * @param {Object} [options={}] - fetch选项
100+ * @returns {Promise<Object>} API响应数据
101+ * @example
102+ * const data = await ExamApiManager.request('/exam/api/questions', { method: 'GET' });
103+ */
104+async function request(url, options = {}) {
105+ // ...
106+}
107+```
35108  
36−1. _select apphost_; use this tool if working with multiple app hosts within a workspace.
37−2. _list apphosts_; use this tool to get details about active app hosts.
109+## AMIS 框架集成
38110  
39−## Playwright MCP server
111+### 主题配置
40112  
41−The playwright MCP server has also been configured in this repository and you should use it to perform functional investigations of the resources defined in the app model as you work on the codebase. To get endpoints that can be used for navigation using the playwright MCP server use the list resources tool.
113+项目使用 **antd** 主题 [[memory:8912919]]:
42114  
43−## Updating the app host
44−The user may request that you update the Aspire apphost. You can do this using the `aspire update` command. This will update the apphost to the latest version and some of the Aspire specific packages in referenced projects, however you may need to manually update other packages in the solution to ensure compatibility. You can consider using the `dotnet-outdated` with the users consent. To install the `dotnet-outdated` tool use the following command:
115+```javascript
116+// 初始化 AMIS
117+let amisScoped = amis.embed('#root', amisJSON, {
118+ location: history.location,
119+ data: {},
120+ context: {
121+ WEB_HOST: webHost
122+ }
123+}, {
124+ theme: 'antd' // 必须使用 antd 主题
125+});
126+```
45127  
128+### 事件系统
129+ 
130+使用 `onEvent` 配置事件监听:
131+ 
132+```javascript
133+{
134+ type: 'form',
135+ api: '/identity/api/identity/auth/login',
136+ onEvent: {
137+ // 表单提交成功事件
138+ submitSucc: {
139+ actions: [
140+ {
141+ actionType: 'custom',
142+ script: `
143+ const token = event.data.token;
144+ TokenManager.setToken(token, 24);
145+ window.location.href = '/';
146+ `
147+ }
148+ ]
149+ },
150+ // 数据初始化完成事件
151+ fetchInited: {
152+ actions: [
153+ {
154+ actionType: 'custom',
155+ script: 'window.fetchUnreadNotificationCount();'
156+ }
157+ ]
158+ }
159+ }
160+}
46161 ```
47−dotnet tool install --global dotnet-outdated-tool
162+ 
163+### 行为类型
164+ 
165+优先使用 AMIS 内置行为类型(actionType):
166+ 
167+```javascript
168+// ✅ 核心行为
169+{ actionType: 'ajax', api: 'POST:/api/submit' }
170+{ actionType: 'link', link: '/dashboard' }
171+{ actionType: 'dialog', dialog: { /* ... */ } }
172+{ actionType: 'reload', target: 'crud' }
173+{ actionType: 'copy', content: '${text}' }
174+ 
175+// ✅ 表单行为
176+{ actionType: 'submit' }
177+{ actionType: 'reset' }
178+{ actionType: 'clear' }
179+ 
180+// ✅ 自定义脚本(仅在必要时使用)
181+{
182+ actionType: 'custom',
183+ script: `
184+ const tenantId = event.data.tenantId;
185+ window.location.href = '/' + tenantId + '/login';
186+ `
187+}
48188 ```
49189  
50−## Persistent containers
51−IMPORTANT! Consider avoiding persistent containers early during development to avoid creating state management issues when restarting the app.
190+### 请求适配器
52191  
53−## Aspire workload
54−IMPORTANT! The aspire workload is obsolete. You should never attempt to install or use the Aspire workload.
192+使用 `requestAdaptor` 和 `adaptor` 处理请求和响应:
55193  
56−## Official documentation
57−IMPORTANT! Always prefer official documentation when available. The following sites contain the official documentation for Aspire and related components
194+```javascript
195+api: {
196+ method: 'post',
197+ url: '/identity/api/identity/auth/login',
198+
199+ // 请求适配器 - 添加认证头
200+ requestAdaptor: function(api) {
201+ const token = TokenManager.getToken();
202+ api.headers = api.headers || {};
203+ api.headers['Authorization'] = token ? 'Bearer ' + token : '';
204+ api.headers['X-Forwarded-With'] = 'CodeSpirit';
205+ api.headers['X-Tenant-Id'] = window.tenantId || 'system';
206+ return api;
207+ },
208+
209+ // 响应适配器 - 处理响应数据
210+ adaptor: function(payload, response, api) {
211+ if (response.status === 401) {
212+ window.location.href = '/login';
213+ return { msg: '登录过期!' };
214+ }
215+
216+ if (payload.status === 0 && payload.data) {
217+ TokenManager.setToken(payload.data.token, 24);
218+ }
219+
220+ return payload;
221+ }
222+}
223+```
58224  
59−1. https://aspire.dev
60−2. https://learn.microsoft.com/dotnet/aspire
61−3. https://nuget.org (for specific integration package details)
225+## Token 管理
62226  
63−## BMAD AI 工作流
227+### TokenManager 使用
64228  
65−本项目已集成 BMAD (Breakthrough Method of Agile AI-Driven Development) 完整工作流,用于结构化的软件开发生命周期管理。
229+使用 `TokenManager` 统一管理认证状态:
66230  
67−### 快速开始
231+```javascript
232+// 初始化模式
233+TokenManager.initSystemMode(); // 系统平台
234+TokenManager.initTenantMode('tenant-id'); // 租户平台
235+TokenManager.initClientMode('tenant-id', 'exam'); // 客户端平台
68236  
69−1. **小型任务/Bug 修复** (Quick Flow):
70− - `/quick-spec` - 创建技术规范
71− - `/quick-dev` - 实现变更
72− - `/code-review` - 代码审查
237+// Token 操作
238+TokenManager.setToken('access-token', 24); // 设置 token(24小时过期)
239+const token = TokenManager.getToken(); // 获取 token
240+TokenManager.clearToken(); // 清除 token
241+TokenManager.hasToken(); // 检查是否有 token
242+TokenManager.isTokenExpired(); // 检查是否过期
243+TokenManager.isAuthenticated(); // 检查是否已认证
73244  
74−2. **完整功能开发** (Full Flow):
75− - `/product-brief` - 产品需求简报
76− - `/create-prd` - 创建 PRD
77− - `/create-architecture` - 架构设计
78− - `/create-epics-and-stories` - 拆分为 Epic 和 Story
79− - `/sprint-planning` - Sprint 规划
80− - `/dev-story` - 实现 Story
81− - `/code-review` - 代码审查
82− - `/retrospective` - 复盘
245+// 扩展功能
246+TokenManager.setTokenExtended(accessToken, refreshToken, expiresIn, tenantId);
247+TokenManager.getRefreshToken();
248+TokenManager.getAuthHeaders(); // 获取认证请求头
249+TokenManager.setUserInfo(userInfo);
250+TokenManager.getUserInfo();
251+```
83252  
84−### 与 CodeSpirit 规范集成
253+### 认证请求头
85254  
86−BMAD 工作流已配置为自动遵循 CodeSpirit 的所有开发规范(位于 `.cursor/rules/`)。在使用 BMAD 时:
255+所有 API 请求必须携带认证头:
87256  
88−- PRD 会自动考虑多租户、多数据库、AI 功能等项目特性
89−- 架构设计会遵循依赖注入、缓存策略等规范
90−- Story 实现会应用正确的命名约定、DTO 设计、控制器规范等
91−- 代码审查会执行 CodeSpirit 特定的审查清单
257+```javascript
258+const headers = {
259+ 'Authorization': token ? 'Bearer ' + token : '',
260+ 'X-Forwarded-With': 'CodeSpirit',
261+ 'X-Tenant-Id': tenantId || 'system',
262+ 'Content-Type': 'application/json'
263+};
264+```
92265  
93−### 获取帮助
266+## API 请求规范
94267  
95−任何时候,输入 `/bmad-help` 可获取上下文相关的指导。
268+### 服务发现路径
96269  
97−详细使用指南请参考:
98−- **[BMAD 使用教程](Docs/bmad/bmad-tutorial.md)** - 完整的综合教程(推荐新手阅读)
99−- [BMAD 工作流指南](Docs/bmad/bmad-workflow-guide.md) - 详细的工作流使用指南
100−- [BMAD 团队培训指南](Docs/bmad/bmad-team-guide.md) - 团队培训材料
101−- [BMAD 集成技能](.cursor/skills/bmad-integration/SKILL.md) - BMAD 与 CodeSpirit 集成
102−- [项目上下文文档](project-context.md) - 项目上下文和规范引用
270+API 路径必须附带服务短名:
271+ 
272+```javascript
273+// ✅ 正确:附带服务名
274+'/identity/api/identity/profile'
275+'/exam/api/exam/questions'
276+'/messaging/api/messaging/messages/my/list'
277+'/survey/api/surveys/${surveyId}'
278+ 
279+// ❌ 错误:缺少服务名
280+'/api/identity/profile'
281+'/api/questions'
282+```
283+ 
284+### API 管理器模式
285+ 
286+使用统一的 API 管理器处理请求:
287+ 
288+```javascript
289+/**
290+ * API管理器
291+ */
292+window.ExamApiManager = {
293+ /**
294+ * 统一的API请求函数
295+ * @param {string} url - API路径
296+ * @param {Object} options - fetch选项
297+ * @returns {Promise} API响应数据
298+ */
299+ request: async function(url, options = {}) {
300+ const token = window.TokenManager?.getToken();
301+
302+ const requestConfig = {
303+ ...options,
304+ headers: {
305+ 'Authorization': token ? 'Bearer ' + token : '',
306+ 'X-Tenant-Id': window.tenantId,
307+ 'X-Forwarded-With': 'CodeSpirit',
308+ 'Content-Type': 'application/json',
309+ ...options.headers
310+ }
311+ };
312+ 
313+ const response = await fetch(url, requestConfig);
314+
315+ // 处理认证失败
316+ if (response.status === 401) {
317+ window.location.href = '/login';
318+ throw new Error('认证失败,请重新登录');
319+ }
320+
321+ if (!response.ok) {
322+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
323+ }
324+
325+ const result = await response.json();
326+
327+ if (result.status !== undefined && result.status !== 0) {
328+ throw new Error(result.msg || '请求失败');
329+ }
330+
331+ return result.data || result;
332+ },
333+ 
334+ get: function(url, options = {}) {
335+ return this.request(url, { ...options, method: 'GET' });
336+ },
337+ 
338+ post: function(url, data = null, options = {}) {
339+ return this.request(url, {
340+ ...options,
341+ method: 'POST',
342+ body: data ? JSON.stringify(data) : undefined
343+ });
344+ }
345+};
346+```
347+ 
348+## 缓存管理
349+ 
350+### 缓存键命名
351+ 
352+使用租户隔离的缓存键:
353+ 
354+```javascript
355+// 格式:{module}_cache_{tenantId}_{key}
356+const cacheKey = `exam_cache_${tenantId}_login_config`;
357+const cacheKey = `survey_cache_${tenantId}_form_data`;
358+```
359+ 
360+### 缓存工具类
361+ 
362+```javascript
363+const CacheManager = {
364+ /**
365+ * 获取缓存数据
366+ * @param {string} key - 缓存key
367+ * @param {string} tenantId - 租户ID
368+ * @returns {Object|null} 缓存的数据
369+ */
370+ get: function(key, tenantId) {
371+ try {
372+ const cacheKey = `exam_cache_${tenantId}_${key}`;
373+ const cached = sessionStorage.getItem(cacheKey);
374+ if (!cached) return null;
375+ 
376+ const data = JSON.parse(cached);
377+
378+ // 检查是否过期
379+ if (data.expiry && Date.now() > data.expiry) {
380+ sessionStorage.removeItem(cacheKey);
381+ return null;
382+ }
383+
384+ return data.value;
385+ } catch (error) {
386+ console.error('[缓存读取失败]', error);
387+ return null;
388+ }
389+ },
390+ 
391+ /**
392+ * 设置缓存数据
393+ * @param {string} key - 缓存key
394+ * @param {string} tenantId - 租户ID
395+ * @param {Object} value - 要缓存的数据
396+ * @param {number} ttl - 过期时间(毫秒)
397+ */
398+ set: function(key, tenantId, value, ttl = 30 * 60 * 1000) {
399+ try {
400+ const cacheKey = `exam_cache_${tenantId}_${key}`;
401+ const data = {
402+ value: value,
403+ expiry: ttl ? Date.now() + ttl : null
404+ };
405+ sessionStorage.setItem(cacheKey, JSON.stringify(data));
406+ } catch (error) {
407+ console.error('[缓存写入失败]', error);
408+ }
409+ }
410+};
411+```
412+ 
413+## 国际化
414+ 
415+使用 `CodeSpirit.i18n` 进行翻译:
416+ 
417+```javascript
418+// 初始化(服务器端调用)
419+window.CodeSpirit.i18n.init('zh-CN', {
420+ 'Login.Title': '用户登录',
421+ 'Login.Success': '登录成功,欢迎 {userName}!'
422+});
423+ 
424+// 获取翻译文本
425+const title = CodeSpirit.i18n.t('Login.Title');
426+const message = CodeSpirit.i18n.t('Login.Success', { userName: 'John' });
427+ 
428+// 切换语言
429+CodeSpirit.i18n.switchLanguage('en');
430+```
431+ 
432+## 类定义规范
433+ 
434+使用 ES6 class 语法:
435+ 
436+```javascript
437+/**
438+ * 通知客户端
439+ * 提供与通知服务的连接和消息处理功能
440+ */
441+class NotificationClient {
442+ /**
443+ * @param {string} hubUrl - SignalR Hub URL
444+ */
445+ constructor(hubUrl = '/notification-hub') {
446+ this.hubUrl = hubUrl;
447+ this.connection = null;
448+ this.handlers = new Map();
449+ }
450+ 
451+ /**
452+ * 连接到通知服务
453+ * @returns {Promise} 连接Promise
454+ */
455+ async connect() {
456+ this.connection = new signalR.HubConnectionBuilder()
457+ .withUrl(this.hubUrl)
458+ .withAutomaticReconnect()
459+ .build();
460+ 
461+ await this.connection.start();
462+ console.log('通知连接已建立');
463+ }
464+ 
465+ /**
466+ * 注册消息处理器
467+ * @param {string} topic - 主题名称
468+ * @param {string} type - 消息类型
469+ * @param {Function} handler - 处理函数
470+ */
471+ on(topic, type, handler) {
472+ const key = `${topic}:${type}`;
473+ if (!this.handlers.has(key)) {
474+ this.handlers.set(key, []);
475+ }
476+ this.handlers.get(key).push(handler);
477+ }
478+}
479+ 
480+window.NotificationClient = NotificationClient;
481+```
482+ 
483+## 代码质量要求
484+ 
485+### 函数长度限制
486+ 
487+一个函数不超过 30 行代码,复杂逻辑拆分为多个小函数:
488+ 
489+```javascript
490+// ✅ 正确:拆分为多个小函数
491+function handleLoginSuccess(payload) {
492+ saveToken(payload.data.token);
493+ redirectToTarget();
494+}
495+ 
496+function saveToken(token) {
497+ TokenManager.setToken(token, 24);
498+}
499+ 
500+function redirectToTarget() {
501+ const redirectUrl = getRedirectUrl();
502+ if (isValidRedirect(redirectUrl)) {
503+ window.location.href = redirectUrl;
504+ } else {
505+ window.location.href = '/';
506+ }
507+}
508+```
509+ 
510+### 样式分离
511+ 
512+样式写入独立的 CSS 文件,不在 JS 中内联样式:
513+ 
514+```javascript
515+// ✅ 正确:使用 CSS 类名
516+element.className = 'survey-container loading';
517+ 
518+// ❌ 避免:内联样式
519+element.style.backgroundColor = '#fff';
520+element.style.padding = '20px';
521+```
522+ 
523+### 响应式支持
524+ 
525+界面需要支持响应式布局和移动端适配:
526+ 
527+```javascript
528+// AMIS 配置中使用响应式断点
529+{
530+ type: 'grid',
531+ columns: [
532+ {
533+ xs: 12, // 手机:全宽
534+ sm: 6, // 平板:半宽
535+ md: 4, // 桌面:三分之一
536+ lg: 3, // 大屏:四分之一
537+ body: { /* ... */ }
538+ }
539+ ]
540+}
541+```
542+ 
543+## 禁止事项
544+ 
545+```javascript
546+// ❌ 禁止:自定义 DOM 事件(使用 AMIS 事件系统)
547+document.getElementById('btn').addEventListener('click', handler);
548+ 
549+// ❌ 禁止:直接操作 DOM(除非必要)
550+document.getElementById('content').innerHTML = html;
551+ 
552+// ❌ 禁止:使用 var 声明变量
553+var token = getToken();
554+ 
555+// ❌ 禁止:缺少错误处理
556+const response = await fetch(url);
557+const data = await response.json();
558+ 
559+// ❌ 禁止:硬编码敏感信息
560+const apiKey = 'sk-xxxxxxxx';
561+```
562+ 
563+## AMIS 官方文档
564+ 
565+- 📖 组件文档:https://aisuda.bce.baidu.com/amis/zh-CN/docs/index
566+- 🎨 事件系统:https://aisuda.bce.baidu.com/amis/zh-CN/components/action#%E4%BA%8B%E4%BB%B6%E8%A1%A8
567+- 🔘 行为按钮:https://aisuda.bce.baidu.com/amis/zh-CN/components/action
568+- 📝 表单组件:https://aisuda.bce.baidu.com/amis/zh-CN/components/form/index
569+ 
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack