@deepstorm/cli 0.1.0
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/README.md +72 -0
- package/dist/agents/reef-inspect-figma.md +77 -0
- package/dist/agents/reef-review-backend.md.tmpl +112 -0
- package/dist/agents/reef-review-frontend.md.tmpl +78 -0
- package/dist/agents/reef-review-infra.md +47 -0
- package/dist/agents/reef-review-security.md.tmpl +80 -0
- package/dist/agents/reef-scope-analysis.md +64 -0
- package/dist/build-registry.js +375 -0
- package/dist/cli.js +8581 -0
- package/dist/config-schema.json +133 -0
- package/dist/env-examples/context7.env-example +19 -0
- package/dist/env-examples/feishu-wiki.env-example +16 -0
- package/dist/env-examples/figma.env-example +16 -0
- package/dist/env-examples/github.env-example +20 -0
- package/dist/env-examples/jira.env-example +20 -0
- package/dist/hooks/mcp-hook.sh +77 -0
- package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
- package/dist/hooks/reef-block-dangerous.sh +70 -0
- package/dist/hooks/reef-hooks.json +72 -0
- package/dist/hooks/reef-intent-detect.sh +129 -0
- package/dist/hooks/reef-protect-files.sh +55 -0
- package/dist/hooks/reef-run-tests.sh +84 -0
- package/dist/hooks/reef-scope-check.sh +386 -0
- package/dist/hooks/reef-scope-ci.sh +28 -0
- package/dist/hooks/reef-scope-gate.sh +115 -0
- package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
- package/dist/hooks/reef-scope-setup.sh +204 -0
- package/dist/hooks/reef-scope-split.sh +203 -0
- package/dist/hooks/sweep-hooks.json +14 -0
- package/dist/hooks/sweep-mcp-hook.sh +77 -0
- package/dist/hooks/tide-hooks.json +14 -0
- package/dist/hooks/tide-session-preload.sh +17 -0
- package/dist/mcp/code-hosting/github.json +20 -0
- package/dist/mcp/design-tools/figma.json +19 -0
- package/dist/mcp/docs-reference/context7.json +28 -0
- package/dist/mcp/e2e-testing/playwright.json +13 -0
- package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
- package/dist/mcp/project-management/jira.json +27 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
- package/dist/registry.json +818 -0
- package/dist/skills/atoll-ops/SKILL.md +46 -0
- package/dist/skills/reef-commit/SKILL.md +127 -0
- package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
- package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
- package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
- package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
- package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
- package/dist/skills/reef-harden/EXAMPLES.md +89 -0
- package/dist/skills/reef-harden/SKILL.md +136 -0
- package/dist/skills/reef-pr/SKILL.md +97 -0
- package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
- package/dist/skills/reef-scope/SKILL.md +134 -0
- package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
- package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
- package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
- package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
- package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
- package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
- package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
- package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
- package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
- package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
- package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
- package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
- package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
- package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
- package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
- package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
- package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
- package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
- package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
- package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
- package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
- package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
- package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
- package/dist/skills/reef-testcase/SKILL.md +138 -0
- package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
- package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
- package/dist/skills/sweep-init/SKILL.md +286 -0
- package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
- package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
- package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
- package/dist/skills/sweep-run/SKILL.md +437 -0
- package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
- package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
- package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
- package/dist/skills/tide-discuss/references/checklists.md +88 -0
- package/dist/skills/tide-discuss/references/data-format.md +237 -0
- package/dist/skills/tide-discuss/references/prd-template.md +134 -0
- package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
- package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
- package/package.json +38 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 前端实体/类型示例
|
|
2
|
+
|
|
3
|
+
## 1. 实体接口层次
|
|
4
|
+
|
|
5
|
+
实体接口镜像后端实体层次,定义在 `shared/base.ts`:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export interface ImmutableEntity { id: number; createdAt: LocalDateTime; }
|
|
9
|
+
export interface Entity extends ImmutableEntity { lastModifiedAt: LocalDateTime; }
|
|
10
|
+
export interface ImmutableAuditable<T> extends ImmutableEntity { createdBy: T; }
|
|
11
|
+
export interface Auditable<T> extends Entity { createdBy: T; lastModifiedBy: T; }
|
|
12
|
+
|
|
13
|
+
// UserSummary(审计字段常用类型)
|
|
14
|
+
export interface UserSummary { id: number; username: string; fullName: string; }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> `LocalDateTime` 来自 `@js-joda/core`,配合 `iso8601Interceptor` 自动从 JSON 反序列化。
|
|
18
|
+
|
|
19
|
+
## 2. 实体定义示例
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// 可变实体(extends Entity)
|
|
23
|
+
export interface FormSummary extends Entity {
|
|
24
|
+
title: string;
|
|
25
|
+
approvalEnabled: boolean;
|
|
26
|
+
printConfig: PrintConfig | null;
|
|
27
|
+
actions: FormAction[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 可审计可变实体(extends Auditable<UserSummary>)
|
|
31
|
+
export interface FormResponse extends Auditable<UserSummary> {
|
|
32
|
+
answers: Partial<Record<string, Answer>>;
|
|
33
|
+
approvalState: ApprovalState | null;
|
|
34
|
+
status: 'NOT_STARTED' | 'RUNNING' | 'DONE' | 'FAILED';
|
|
35
|
+
formRevisionId: number;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 3. 分页响应
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
export interface PageRequest { page: number; size: number; sort: string; }
|
|
43
|
+
export interface PagedResponse<T extends ImmutableEntity> {
|
|
44
|
+
items: T[];
|
|
45
|
+
totalItems: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 将 PrimeNG p-table (lazy) 的 TableLazyLoadEvent 转换为后端分页参数
|
|
49
|
+
export function toPageRequest(event: TableLazyLoadEvent): PageRequest {
|
|
50
|
+
const { first, rows, sortField, sortOrder } = event;
|
|
51
|
+
return {
|
|
52
|
+
page: first! / rows!,
|
|
53
|
+
size: rows!,
|
|
54
|
+
sort: `${Array.isArray(sortField) ? sortField.join(',') : sortField},${sortOrder === -1 ? 'desc' : 'asc'}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Service 中使用
|
|
59
|
+
@Injectable({ providedIn: 'root' })
|
|
60
|
+
export class DataObjectInstanceService {
|
|
61
|
+
private readonly http = inject(HttpClient);
|
|
62
|
+
|
|
63
|
+
list(objectId: signal<number | undefined>) {
|
|
64
|
+
return httpResource<PagedResponse<DataObjectInstance>>(() => {
|
|
65
|
+
const id = objectId();
|
|
66
|
+
return id !== undefined
|
|
67
|
+
? `/api/v1/apps/${this.appService.appId()}/objects/${id}/instances`
|
|
68
|
+
: undefined;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 4. Discriminated Union 模式
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// 用户类型(字符串字面量 discrimination)
|
|
78
|
+
interface BaseUser extends Entity {
|
|
79
|
+
type: string; username: string; fullName: string; role: Role; disabled: boolean;
|
|
80
|
+
}
|
|
81
|
+
export interface InternalUser extends BaseUser { type: 'InternalUser'; }
|
|
82
|
+
export interface CdyxUser extends BaseUser { type: 'CdyxUser'; }
|
|
83
|
+
export type User = InternalUser | CdyxUser;
|
|
84
|
+
|
|
85
|
+
// 字段值类型(按 type 字段分发)
|
|
86
|
+
export type FieldValue = TextValue | NumberValue | BooleanValue;
|
|
87
|
+
export interface TextValue { type: 'text'; value: string | null; }
|
|
88
|
+
export interface NumberValue { type: 'number'; value: number | null; }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 5. 关键规则
|
|
92
|
+
|
|
93
|
+
| 规则 | 说明 |
|
|
94
|
+
|------|------|
|
|
95
|
+
| **interface,不是 class** | 实体全是 `interface`,没有 `class` |
|
|
96
|
+
| **类型参数 `T`** | 审计字段的 `T` 通常是 `UserSummary` |
|
|
97
|
+
| **必要字段** | 字段不设可选(`?`),后端保证返回 |
|
|
98
|
+
| **枚举用 union** | 状态字段用 literal union,不用 TypeScript `enum` |
|
|
99
|
+
| **字段注释** | Entity/接口字段加 `/** */` 注释;简单的 DTO 不加 |
|
|
100
|
+
| **参数顺序** | `appId` → 父级 ID → 自身 ID → name/描述 |
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# 前端 Signal Forms 示例
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 1. 标准 Signal Forms 组件(Dialog CRUD)
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { form, FormField, FormRoot, required } from '@angular/forms/signals';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'app-crud-dialog',
|
|
12
|
+
imports: [Button, Dialog, FormField, FormRoot, InputText],
|
|
13
|
+
template: `
|
|
14
|
+
<p-button label="创建" (onClick)="onCreate()" />
|
|
15
|
+
<p-dialog
|
|
16
|
+
[(visible)]="visible"
|
|
17
|
+
[header]="isEditing() ? '编辑' : '创建'"
|
|
18
|
+
modal
|
|
19
|
+
(onHide)="onHide()"
|
|
20
|
+
>
|
|
21
|
+
<form [formRoot]="form" (submit)="submitted.set(true)">
|
|
22
|
+
<input
|
|
23
|
+
pInputText
|
|
24
|
+
[formField]="form.name"
|
|
25
|
+
[pInvalid]="submitted() && form.name().invalid()"
|
|
26
|
+
/>
|
|
27
|
+
@if (submitted() && form.name().invalid()) {
|
|
28
|
+
<app-error-messages [errors]="form.name().errors()" />
|
|
29
|
+
}
|
|
30
|
+
<p-button type="submit" label="保存" />
|
|
31
|
+
</form>
|
|
32
|
+
</p-dialog>
|
|
33
|
+
`,
|
|
34
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
35
|
+
})
|
|
36
|
+
export class CrudDialogComponent {
|
|
37
|
+
private readonly messageService = inject(MessageService);
|
|
38
|
+
|
|
39
|
+
readonly visible = signal(false);
|
|
40
|
+
readonly isEditing = signal(false);
|
|
41
|
+
readonly submitted = signal(false);
|
|
42
|
+
|
|
43
|
+
readonly form = form(signal({ name: '' }), (path) => {
|
|
44
|
+
required(path.name, { message: '名称不能为空' });
|
|
45
|
+
}, {
|
|
46
|
+
submission: { action: async () => {
|
|
47
|
+
await lastValueFrom(this.service.create(this.form().value()));
|
|
48
|
+
this.messageService.add({ severity: 'success', summary: '创建成功' });
|
|
49
|
+
this.visible.set(false);
|
|
50
|
+
}},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
onCreate() {
|
|
54
|
+
this.isEditing.set(false);
|
|
55
|
+
this.form().reset({ name: '' });
|
|
56
|
+
this.visible.set(true);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onEdit(item: Item) {
|
|
60
|
+
this.isEditing.set(true);
|
|
61
|
+
this.form().reset({ name: item.name });
|
|
62
|
+
this.visible.set(true);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onHide() {
|
|
66
|
+
this.submitted.set(false);
|
|
67
|
+
this.visible.set(false);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 2. 复合表单模型(条件隐藏/禁用)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
interface FormModel {
|
|
78
|
+
type: 'mysql' | 'postgres' | 'http';
|
|
79
|
+
name: string;
|
|
80
|
+
db: { host: string; port: number | null };
|
|
81
|
+
http: { baseUrl: string };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
readonly mode = signal<'create' | 'edit'>('create');
|
|
85
|
+
|
|
86
|
+
readonly form = form(signal(domainModelToFormModel(null)), (path) => {
|
|
87
|
+
required(path.name, { message: '名称不能为空' });
|
|
88
|
+
disabled(path.type, () => this.mode() === 'edit');
|
|
89
|
+
hidden(path.db, ({ valueOf }) => valueOf(path.type) === 'http');
|
|
90
|
+
hidden(path.http, ({ valueOf }) => valueOf(path.type) !== 'http');
|
|
91
|
+
required(path.http.baseUrl, { message: '基础 URL 不能为空' });
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 3. Signal Forms 异步提交(带 loading)
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
readonly form = form(signal(EMPTY_USER), (path) => {
|
|
101
|
+
required(path.username, { message: '用户名不能为空' });
|
|
102
|
+
}, {
|
|
103
|
+
submission: {
|
|
104
|
+
action: async () => {
|
|
105
|
+
this.loading.set(true);
|
|
106
|
+
try {
|
|
107
|
+
const user = await lastValueFrom(
|
|
108
|
+
this.authService.createUser(this.form().value())
|
|
109
|
+
);
|
|
110
|
+
this.messageService.add({ severity: 'success', summary: '创建成功' });
|
|
111
|
+
this.visible.set(false);
|
|
112
|
+
this.users.update(users => [...users, user]);
|
|
113
|
+
} finally {
|
|
114
|
+
this.loading.set(false);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# 前端 Service 层与路由示例
|
|
2
|
+
|
|
3
|
+
## 1. httpResource 服务层封装
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
@Injectable({ providedIn: 'root' })
|
|
7
|
+
export class ItemService {
|
|
8
|
+
private readonly http = inject(HttpClient);
|
|
9
|
+
|
|
10
|
+
private readonly itemsResource = httpResource<ListResponse>(() => '/api/v1/items');
|
|
11
|
+
|
|
12
|
+
// 只读暴露给组件
|
|
13
|
+
readonly items = computed<Item[]>(() => this.itemsResource.value()?.items ?? []);
|
|
14
|
+
readonly loading = this.itemsResource.isLoading;
|
|
15
|
+
|
|
16
|
+
/** 写操作:HttpClient + finalize reload */
|
|
17
|
+
create(request: { name: string }) {
|
|
18
|
+
return this.http.post<Item>('/api/v1/items', request).pipe(
|
|
19
|
+
finalize(() => this.itemsResource.reload()),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
delete(id: number) {
|
|
24
|
+
return this.http.delete(`/api/v1/items/${id}`).pipe(
|
|
25
|
+
finalize(() => this.itemsResource.reload()),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 2. 模板控制流
|
|
32
|
+
|
|
33
|
+
```angular2html
|
|
34
|
+
@for (item of items(); track item.id) {
|
|
35
|
+
@switch (item.type) {
|
|
36
|
+
@case ('TextControl') {
|
|
37
|
+
@let label = item.label || '未命名';
|
|
38
|
+
<app-text-control [label]="label" />
|
|
39
|
+
}
|
|
40
|
+
@case ('ChoiceControl') {
|
|
41
|
+
<app-select [options]="item.options" />
|
|
42
|
+
}
|
|
43
|
+
@default {
|
|
44
|
+
<div>未知控件: {{ item.type }}</div>
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 3. 路由配置模式
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export const routes: Routes = [
|
|
54
|
+
{
|
|
55
|
+
path: '',
|
|
56
|
+
component: DefaultLayoutComponent,
|
|
57
|
+
canActivate: [authGuard],
|
|
58
|
+
children: [
|
|
59
|
+
{
|
|
60
|
+
path: 'dashboard',
|
|
61
|
+
title: '仪表盘',
|
|
62
|
+
loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
path: 'admin',
|
|
68
|
+
component: DefaultLayoutComponent,
|
|
69
|
+
canActivate: [authGuard],
|
|
70
|
+
data: { requiredAuthority: 'ADMIN' },
|
|
71
|
+
children: [
|
|
72
|
+
{
|
|
73
|
+
path: 'users',
|
|
74
|
+
title: '用户管理',
|
|
75
|
+
loadComponent: () => import('./admin/user-list/user-list.component').then(m => m.UserListComponent),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
path: 'role-mappings',
|
|
79
|
+
canActivate: [authGuard],
|
|
80
|
+
data: { requiredAuthority: 'ADMIN', requireRootTenant: true },
|
|
81
|
+
loadComponent: () => import('./admin/role-mappings/role-mappings.component').then(m => m.RoleMappingsComponent),
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: 'login',
|
|
87
|
+
loadComponent: () => import('./login/login.component').then(m => m.LoginComponent),
|
|
88
|
+
canActivate: [authGuard],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
path: '**',
|
|
92
|
+
loadComponent: () => import('./page-not-found/page-not-found.component').then(m => m.PageNotFoundComponent),
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
```
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 前端编码快速参考
|
|
2
|
+
|
|
3
|
+
按需加载。仅当你需要编写对应组件类型时阅读相关章节。
|
|
4
|
+
|
|
5
|
+
> **完整示例代码见 `examples/` 目录。** 已安装子维度的规范,通过该维度的 `{value}.md` 文件阅读。
|
|
6
|
+
|
|
7
|
+
## 速查
|
|
8
|
+
|
|
9
|
+
| 场景 | 决策 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 新建组件 | Standalone + OnPush + inline template + inject() |
|
|
12
|
+
| 新建表单 | Signal Forms(`form()`),非 Reactive Forms |
|
|
13
|
+
| 读数据 | `httpResource()` |
|
|
14
|
+
| 写数据 | `HttpClient` + `finalize(() => resource.reload())` |
|
|
15
|
+
| Service 职责 | 只负责网络请求,不管理 UI 状态 |
|
|
16
|
+
| 路由 | `loadComponent` 懒加载 |
|
|
17
|
+
|
|
18
|
+
## 新建组件
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'app-example',
|
|
23
|
+
imports: [/* 显式导入 */],
|
|
24
|
+
template: `...`,
|
|
25
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
26
|
+
host: { class: 'flex flex-col gap-2 p-4' },
|
|
27
|
+
})
|
|
28
|
+
export class ExampleComponent {
|
|
29
|
+
private readonly router = inject(Router);
|
|
30
|
+
submitted = output<number>();
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| 规则 | 要求 |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Standalone | 禁止 NgModule,`imports: []` 显式导入 |
|
|
37
|
+
| `OnPush` | 模板中所有输入 immutable |
|
|
38
|
+
| `inject()` | 禁止构造函数注入 |
|
|
39
|
+
| `output()` | 禁止 `@Output()` + `EventEmitter` |
|
|
40
|
+
| `host` | 容器样式用 `host: { class: '...' }`,不包裹 `<div>` |
|
|
41
|
+
|
|
42
|
+
## Signal 状态
|
|
43
|
+
|
|
44
|
+
响应式状态用 `signal()` / `computed()` / `linkedSignal()`;私有 signal 对外暴露用 `asReadonly()`;模板中必须调用 signal(写 `signal()`,不要写 `signal`)。
|
|
45
|
+
|
|
46
|
+
### Signal Forms(新表单首选)
|
|
47
|
+
|
|
48
|
+
使用 `@angular/forms/signals` 的 `form()`,不再新增 `FormGroup`/`FormBuilder`:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
readonly form = form(signal({ name: '', email: '' }), (path) => {
|
|
52
|
+
required(path.name);
|
|
53
|
+
email(path.email);
|
|
54
|
+
}, {
|
|
55
|
+
submission: { action: async () => {
|
|
56
|
+
await lastValueFrom(this.service.create(this.form().value()));
|
|
57
|
+
}},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## HTTP / Service
|
|
62
|
+
|
|
63
|
+
- **读请求**:`httpResource()` — URL 函数返回 `undefined` 时跳过请求
|
|
64
|
+
- **写请求**:`HttpClient` + `finalize(() => resource.reload())`
|
|
65
|
+
- **Service 职责**:只做网络请求,不管理 UI 状态
|
|
66
|
+
- **Blob 下载错误处理**:`responseType: 'blob'` 时后端 JSON 错误被包装为 Blob,需在 error handler 中解析
|
|
67
|
+
- **API 路径**:基础前缀 `/api/v1/`;自定义方法用 AIP-136 冒号语法
|
|
68
|
+
|
|
69
|
+
## 错误处理
|
|
70
|
+
|
|
71
|
+
- `errorHandlingInterceptor` 已全局注册,统一显示 toast;组件层不额外 `catchError`
|
|
72
|
+
- `iso8601Interceptor`:自动将 ISO 8601 字符串转换为 `@js-joda/core` 类型
|
|
73
|
+
- 401 由 auth guard 处理,拦截器跳过
|
|
74
|
+
- 表单验证错误用 `<app-error-messages>` 组件渲染
|
|
75
|
+
|
|
76
|
+
## 路由
|
|
77
|
+
|
|
78
|
+
- 所有功能组件用 `loadComponent` 懒加载
|
|
79
|
+
- 权限通过路由 `data.requiredAuthority` / `data.requireRootTenant` 控制
|
|
80
|
+
- `authGuard` 在每个受保护路由独立注册,不在根路由全局注册
|
|
81
|
+
|
|
82
|
+
## 代码风格
|
|
83
|
+
|
|
84
|
+
- 控制流语句必须使用大括号,禁止无大括号的早期返回
|
|
85
|
+
- 代码折行规则(90 列限制)详见 `examples/code-wrapping.md`
|
|
86
|
+
|
|
87
|
+
## 前端实体类型
|
|
88
|
+
|
|
89
|
+
实体接口镜像后端实体层次,定义在 `shared/base.ts`:`ImmutableEntity` → `Entity` → `Auditable<T>` / `ImmutableAuditable<T>`。完整定义、分页响应、Discriminated Union 模式见 `examples/entity-types.md`。
|
|
90
|
+
|
|
91
|
+
## 常见坑
|
|
92
|
+
|
|
93
|
+
| 场景 | 问题 | 正确做法 |
|
|
94
|
+
|------|------|---------|
|
|
95
|
+
| Blob 下载 | 后端返回 JSON 时 err.error 被包装为 Blob | `err.error.text().then(text => JSON.parse(text))` |
|
|
96
|
+
| httpResource 初始化 | URL 返回 undefined 时 res.value() 为 undefined | `computed(() => res.value()?.items ?? [])` |
|
|
97
|
+
| `form().reset()` | 只传部分字段,缺失字段不被重置 | `form().reset({ name: '' })` 补全所有字段 |
|
|
98
|
+
| Service 状态泄漏 | 在 Service 中放 signal 管理 UI 状态 | Service 只做网络请求,状态放在 Component 中 |
|
|
99
|
+
| `model()` vs `output()` | 需双向绑定时用了 output() | 用 `model()`,父组件 `[(visible)]="xxx"` |
|
|
100
|
+
| submit handler | 未阻止默认提交行为 | 用 `FormRoot` 指令,或 `(submit)="check()"` + 检查 valid |
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reef-testcase
|
|
3
|
+
description: 基于 Jira Issue 生成结构化测试用例清单。从 Jira 功能描述和 PRD 上下文中提取测试场景,覆盖正常流程、边界条件、异常场景、验收标准四维。输出格式兼容 superpowers,可辅助生成更完备的单元测试。
|
|
4
|
+
allowed-tools: Read, Write, Edit, WebFetch, Agent
|
|
5
|
+
deepstorm:
|
|
6
|
+
tool: reef
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Test Case Generation
|
|
10
|
+
|
|
11
|
+
## 功能概述
|
|
12
|
+
|
|
13
|
+
根据 Jira Issue 描述和可选的 PRD 上下文,生成结构化测试用例清单。
|
|
14
|
+
|
|
15
|
+
**核心理念:** 测试用例不是最终产出,而是开发过程的"脚手架"——帮助开发者和 AI 想清楚边界条件、异常场景、验收标准,从而产出更高质量的代码和单元测试。
|
|
16
|
+
|
|
17
|
+
### 工作流
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
flowchart LR
|
|
21
|
+
INPUT["Jira Issue 描述"] --> PARSE["解析功能范围"]
|
|
22
|
+
PRD["PRD 上下文(可选)"] --> PARSE
|
|
23
|
+
PARSE --> ANALYZE["四维覆盖分析"]
|
|
24
|
+
ANALYZE --> OUTPUT["结构化测试用例清单"]
|
|
25
|
+
OUTPUT --> SUPER["→ superpowers 生成单元测试"]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 使用方式
|
|
31
|
+
|
|
32
|
+
| 方式 | 说明 | 示例 |
|
|
33
|
+
|------|------|------|
|
|
34
|
+
| **提供功能描述**(推荐) | 直接描述功能,自动解析生成 | "帮我生成注册功能的测试用例:邮箱+密码+验证码注册" |
|
|
35
|
+
| **Jira + PRD** | 附上 PRD 链接获取更全面覆盖 | "Jira LC-1234:用户注册。PRD 说支持国际手机号,验证码 5 分钟有效" |
|
|
36
|
+
| **Jira ID 参考** | 仅提 ID,引导补充描述 | "为 LC-1234 生成测试用例"(需用户补充描述) |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 工作流
|
|
41
|
+
|
|
42
|
+
### 步骤 1:输入收集
|
|
43
|
+
|
|
44
|
+
#### 1.1 获取 Jira Issue 描述
|
|
45
|
+
|
|
46
|
+
从用户输入中提取功能描述。完整描述直接解析使用;Issue ID 引导补充;简短描述按原文处理。提取**功能范围**、**用户故事**、**验收标准**。
|
|
47
|
+
|
|
48
|
+
#### 1.2 获取 PRD 上下文(可选)
|
|
49
|
+
|
|
50
|
+
从 PRD 提取需求背景、业务规则、约束条件、非功能需求,与 Jira 描述合并形成完整输入。无 PRD 时仅基于 Jira 描述生成,输出标注"上下文受限"。
|
|
51
|
+
|
|
52
|
+
#### 1.3 需求澄清
|
|
53
|
+
|
|
54
|
+
针对模糊点向用户提问:核心用户?边界条件/限制?已知异常场景?
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### 步骤 2:四维覆盖分析
|
|
59
|
+
|
|
60
|
+
基于收集到的输入,按以下四个维度分析测试覆盖:
|
|
61
|
+
|
|
62
|
+
| 维度 | 分析目标 | 来源 |
|
|
63
|
+
|------|---------|------|
|
|
64
|
+
| **正常流程** | 用户按预期操作的完整成功路径 | Jira 功能描述中的主要用户场景 |
|
|
65
|
+
| **边界条件** | 输入值、状态或数据的极限条件 | 规格中的限制、约束、最大/最小值 |
|
|
66
|
+
| **异常场景** | 出错的路径、故障、非预期操作 | 错误处理、网络超时、权限不足、数据不存在 |
|
|
67
|
+
| **验收标准验证** | 逐条验证定义的验收条件 | PRD/Jira 中的 AC 列表 |
|
|
68
|
+
|
|
69
|
+
**覆盖度要求:** 至少覆盖三个维度(必须包含"正常流程"和"验收标准")。
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### 步骤 3:结构化测试用例生成
|
|
74
|
+
|
|
75
|
+
#### 3.1 格式规范
|
|
76
|
+
|
|
77
|
+
每个测试用例包含以下字段:
|
|
78
|
+
|
|
79
|
+
| 字段 | 必填 | 说明 |
|
|
80
|
+
|------|------|------|
|
|
81
|
+
| `ID` | 是 | 唯一标识,格式 `TC-{feature}-{seq}` |
|
|
82
|
+
| `类型` | 是 | 正常流程 / 边界条件 / 异常场景 / 验收标准 |
|
|
83
|
+
| `前置条件` | 是 | 测试所需的前置状态或数据 |
|
|
84
|
+
| `测试步骤` | 是 | 清晰的操作步骤描述 |
|
|
85
|
+
| `预期结果` | 是 | 期望的行为或输出 |
|
|
86
|
+
| `关联验收标准` | 否 | 对应 PRD 或 Jira 中的验收标准编号 |
|
|
87
|
+
|
|
88
|
+
#### 3.2 输出结构
|
|
89
|
+
|
|
90
|
+
```markdown
|
|
91
|
+
## 测试用例清单
|
|
92
|
+
|
|
93
|
+
**功能:** {feature name}
|
|
94
|
+
**来源:** {Jira Issue ID}
|
|
95
|
+
**PRD 上下文:** {PRD link / "无"}
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 正常流程
|
|
100
|
+
|
|
101
|
+
{正常流程测试用例列表}
|
|
102
|
+
|
|
103
|
+
### 边界条件
|
|
104
|
+
|
|
105
|
+
{边界条件测试用例列表}
|
|
106
|
+
|
|
107
|
+
### 异常场景
|
|
108
|
+
|
|
109
|
+
{异常场景测试用例列表}
|
|
110
|
+
|
|
111
|
+
### 验收标准验证
|
|
112
|
+
|
|
113
|
+
{验收标准测试用例列表}
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
> **superpowers 提示:** 以上测试用例清单可作为 superpowers 的输入,辅助生成更完备的单元测试。建议将本段内容复制到开发提示中作为上下文。
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### 3.3 特殊处理
|
|
121
|
+
|
|
122
|
+
无 PRD → 标注"上下文受限,覆盖仅限于 Jira Issue 描述";无验收标准 → 跳过该维度并标注;单维度用例 ≤ 10 条。
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 关键原则
|
|
127
|
+
|
|
128
|
+
- **按需生成**:开发过程中调用,非批量输出
|
|
129
|
+
- **人工 Review**:AI 产出框架,人工审核补充
|
|
130
|
+
- **开发侧内聚**:测试用例是开发脚手架,辅助 superpowers 生成更完备的单元测试
|
|
131
|
+
|
|
132
|
+
## 字段模板
|
|
133
|
+
|
|
134
|
+
测试用例字段模板详见 `references/test-case-template.md`。
|
|
135
|
+
|
|
136
|
+
## 覆盖维度指引
|
|
137
|
+
|
|
138
|
+
四维覆盖的详细指引和检查清单详见 `references/coverage-dimensions.md`。
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# 测试覆盖四维指引
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
生成测试用例时,需从以下四个维度覆盖被测功能。至少覆盖三个维度(包含"正常流程"和"验收标准"为最低要求)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 维度一:正常流程
|
|
10
|
+
|
|
11
|
+
覆盖功能的预期使用路径,即 "Happy Path"。
|
|
12
|
+
|
|
13
|
+
**关键问题:**
|
|
14
|
+
- 用户按预期操作时会发生什么?
|
|
15
|
+
- 最常见的成功路径是什么?
|
|
16
|
+
- 输入有效数据时系统如何响应?
|
|
17
|
+
|
|
18
|
+
**示例:**
|
|
19
|
+
```
|
|
20
|
+
ID: TC-login-001 | 类型: 正常流程
|
|
21
|
+
前置条件: 用户已注册且账号正常
|
|
22
|
+
测试步骤: 1. 输入正确用户名和密码 → 2. 点击登录
|
|
23
|
+
预期结果: 成功登录,跳转到首页
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 维度二:边界条件
|
|
29
|
+
|
|
30
|
+
覆盖输入值、状态或数据在极限条件下的行为。
|
|
31
|
+
|
|
32
|
+
**关键问题:**
|
|
33
|
+
- 输入允许的最大值/最小值?
|
|
34
|
+
- 字符串为空或达到最大长度?
|
|
35
|
+
- 列表为空的边界?
|
|
36
|
+
- 数值为 0 或负数?
|
|
37
|
+
- 时间边界(过去/未来/并发)?
|
|
38
|
+
|
|
39
|
+
**示例:**
|
|
40
|
+
```
|
|
41
|
+
ID: TC-login-002 | 类型: 边界条件
|
|
42
|
+
前置条件: 用户已注册
|
|
43
|
+
测试步骤: 1. 输入空密码 → 2. 点击登录
|
|
44
|
+
预期结果: 提示"密码不能为空",禁止提交
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 维度三:异常场景
|
|
50
|
+
|
|
51
|
+
覆盖可能出错的路径、系统故障或非预期操作。
|
|
52
|
+
|
|
53
|
+
**关键问题:**
|
|
54
|
+
- 网络中断或超时?
|
|
55
|
+
- 数据不存在或已被删除?
|
|
56
|
+
- 权限不足?
|
|
57
|
+
- 并发操作冲突?
|
|
58
|
+
- 输入格式错误或非法字符?
|
|
59
|
+
- 第三方服务不可用?
|
|
60
|
+
|
|
61
|
+
**示例:**
|
|
62
|
+
```
|
|
63
|
+
ID: TC-login-003 | 类型: 异常场景
|
|
64
|
+
前置条件: 用户已注册但账号被锁定
|
|
65
|
+
测试步骤: 1. 输入正确的用户名和密码 → 2. 点击登录
|
|
66
|
+
预期结果: 提示"账号已被锁定,请联系管理员"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 维度四:验收标准验证
|
|
72
|
+
|
|
73
|
+
逐条验证 PRD 或 Jira 中定义的验收标准(Acceptance Criteria)是否满足。
|
|
74
|
+
|
|
75
|
+
**关键问题:**
|
|
76
|
+
- Jira Issue 中列出了哪些 AC?
|
|
77
|
+
- PRD 中有哪些功能验收点?
|
|
78
|
+
- 每条 AC 是否有对应的测试用例验证?
|
|
79
|
+
|
|
80
|
+
**示例:**
|
|
81
|
+
```
|
|
82
|
+
ID: TC-login-004 | 类型: 验收标准
|
|
83
|
+
前置条件: 用户已注册
|
|
84
|
+
测试步骤: 1. 输入正确凭据 → 2. 勾选"记住我" → 3. 点击登录 → 4. 关闭浏览器 → 5. 重新打开登录页
|
|
85
|
+
预期结果: 用户仍处于登录状态,无需重新输入密码(验证 AC-3: 记住登录状态)
|
|
86
|
+
关联验收标准: AC-3
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 覆盖检查清单
|
|
92
|
+
|
|
93
|
+
生成后,检查以下问题:
|
|
94
|
+
|
|
95
|
+
- [ ] **正常流程**:是否覆盖了至少一个完整成功路径?
|
|
96
|
+
- [ ] **边界条件**:是否检查了输入/状态的关键边界?
|
|
97
|
+
- [ ] **异常场景**:是否覆盖了至少一个出错路径?
|
|
98
|
+
- [ ] **验收标准**:是否每条 AC 都有对应测试用例?
|
|
99
|
+
- [ ] **至少三个维度**:覆盖度是否 ≥ 3/4?
|