adspecs 0.1.19 → 0.1.21

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.
Files changed (74) hide show
  1. package/.adspecs/feature.json +16 -16
  2. package/.adspecs/feature.yml +28 -28
  3. package/.adspecs/paths.json +17 -17
  4. package/.adspecs/templates/04-/345/211/215/347/253/257/345/212/237/350/203/275/350/256/276/350/256/241/346/250/241/346/235/277.md +1 -1
  5. package/.adspecs/templates/05-/345/220/216/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +613 -724
  6. package/.adspecs/templates/05b-/345/211/215/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +51 -51
  7. package/.claude-plugin/marketplace.json +23 -23
  8. package/.claude-plugin/plugin.json +18 -18
  9. package/.qoder-plugin/plugin.json +31 -31
  10. package/CLAUDE.md +1 -5
  11. package/INSTALL.md +3 -3
  12. package/README.md +395 -395
  13. package/bin/adspecs.js +129 -129
  14. package/hooks/commit-queue.js +245 -245
  15. package/hooks/hooks.json +63 -63
  16. package/hooks/session-start.js +44 -44
  17. package/hooks/wiki-queue.js +127 -127
  18. package/package.json +61 -61
  19. package/references/ant6-front-standard/index.md +99 -99
  20. package/references/antd-front-demo/public/mockServiceWorker.js +361 -361
  21. package/references/ecp-end-standard/index.md +63 -63
  22. package/references/python-end-standard/01-Python/345/220/216/347/253/257/347/274/226/347/240/201/350/247/204/350/214/203.md +372 -372
  23. package/references/python-end-standard/02-/346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/344/270/216/344/275/277/347/224/250/350/247/204/350/214/203.md +226 -226
  24. package/references/python-end-standard/03-Celery/345/274/202/346/255/245/344/273/273/345/212/241/350/247/204/350/214/203.md +237 -237
  25. package/references/python-end-standard/04-Redis/344/275/277/347/224/250/350/247/204/350/214/203.md +231 -231
  26. package/scripts/postinstall.js +107 -107
  27. package/scripts/sync-version.js +105 -105
  28. package/skills/.claude/.wiki-update-queue +26 -26
  29. package/skills/adspecs-constitution/SKILL.md +157 -0
  30. package/skills/adspecs-export-word/SKILL.md +498 -498
  31. package/skills/adspecs-export-word/references/md-to-docx.js +862 -862
  32. package/skills/adspecs-export-word/references/package-lock.json +220 -220
  33. package/skills/adspecs-export-word/references/package.json +10 -10
  34. package/skills/adspecs-front-prototype/SKILL.md +405 -405
  35. package/skills/adspecs-front-spec/SKILL.md +4 -4
  36. package/skills/adspecs-front-tasks/SKILL.md +213 -173
  37. package/skills/adspecs-plan/SKILL.md +59 -69
  38. package/skills/adspecs-prd/SKILL.md +13 -5
  39. package/skills/adspecs-prd-to-demo/SKILL.md +532 -0
  40. package/skills/adspecs-tasks/SKILL.md +175 -204
  41. package/skills/adspecs-update-status/SKILL.md +382 -382
  42. package/skills/adspecs-utest/SKILL.md +107 -116
  43. package/skills/grill-me/SKILL.md +7 -0
  44. package/skills/grill-me/agents/openai.yaml +5 -0
  45. package/skills/playwright-cli/SKILL.md +420 -0
  46. package/skills/playwright-cli/references/element-attributes.md +23 -0
  47. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  48. package/skills/playwright-cli/references/request-mocking.md +87 -0
  49. package/skills/playwright-cli/references/running-code.md +241 -0
  50. package/skills/playwright-cli/references/session-management.md +225 -0
  51. package/skills/playwright-cli/references/storage-state.md +275 -0
  52. package/skills/playwright-cli/references/test-generation.md +433 -0
  53. package/skills/playwright-cli/references/tracing.md +139 -0
  54. package/skills/playwright-cli/references/video-recording.md +143 -0
  55. package/skills/playwright-trace/SKILL.md +171 -0
  56. package/skills/project-init/SKILL.md +93 -22
  57. package/skills/project-init/references/front-demo/.claude/settings.local.json +9 -0
  58. package/skills/wiki-update/SKILL.md +232 -232
  59. package/src/commands/doctor.js +197 -197
  60. package/src/commands/init.js +83 -83
  61. package/src/commands/plugin.js +165 -165
  62. package/src/commands/update.js +87 -87
  63. package/src/lib/area-scanner.js +129 -129
  64. package/src/lib/copier.js +104 -104
  65. package/src/lib/dir-utils.js +161 -133
  66. package/src/lib/json-merge.js +114 -114
  67. package/src/lib/paths-defaults.js +37 -37
  68. package/src/lib/prompts.js +428 -347
  69. package/src/lib/readme-gen.js +143 -143
  70. package/src/lib/report.js +338 -327
  71. package/src/lib/scaffolder.js +551 -518
  72. package/src/lib/short-name.js +36 -36
  73. package/src/utils.js +80 -80
  74. package/references/antd-front-demo/.env +0 -15
@@ -1,226 +1,226 @@
1
- # 数据库设计与使用规范
2
-
3
- > **版本**: v1.0 | **修订日期**: 2026-07-15
4
- > **技术栈**: SQLAlchemy 2.0+ / Alembic / SQLite (aiosqlite)
5
-
6
- ---
7
-
8
- ## 一、SQLAlchemy 基类
9
-
10
- ### 1.1 声明式基类
11
-
12
- ```python
13
- # app/models/base.py
14
- from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
15
- from sqlalchemy import Integer, String, Boolean, DateTime, func
16
- from datetime import datetime
17
-
18
- class Base(DeclarativeBase):
19
- """SQLAlchemy 声明式基类"""
20
- pass
21
-
22
- class TimestampMixin:
23
- """时间戳 Mixin — 自动记录创建/更新时间"""
24
- created_at: Mapped[datetime] = mapped_column(
25
- DateTime, server_default=func.now(), nullable=False
26
- )
27
- updated_at: Mapped[datetime] = mapped_column(
28
- DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
29
- )
30
-
31
- class SoftDeleteMixin:
32
- """软删除 Mixin"""
33
- is_deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
34
-
35
- class AuditMixin:
36
- """审计 Mixin — 记录操作人"""
37
- created_by: Mapped[int | None] = mapped_column(Integer, nullable=True)
38
- updated_by: Mapped[int | None] = mapped_column(Integer, nullable=True)
39
- ```
40
-
41
- ### 1.2 模型定义示例
42
-
43
- ```python
44
- # app/models/warehouse.py
45
- from sqlalchemy import String, Integer, Text, Index
46
- from sqlalchemy.orm import Mapped, mapped_column, relationship
47
- from app.models.base import Base, TimestampMixin, SoftDeleteMixin, AuditMixin
48
-
49
- class Warehouse(Base, TimestampMixin, SoftDeleteMixin, AuditMixin):
50
- """仓库模型"""
51
- __tablename__ = "warehouses"
52
- __table_args__ = (
53
- Index("ix_warehouse_code", "code", unique=True),
54
- Index("ix_warehouse_active", "is_active"),
55
- )
56
-
57
- id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
58
- code: Mapped[str] = mapped_column(String(50), nullable=False, comment="仓库编码")
59
- name: Mapped[str] = mapped_column(String(200), nullable=False, comment="仓库名称")
60
- location: Mapped[str | None] = mapped_column(String(500), comment="位置")
61
- is_active: Mapped[bool] = mapped_column(default=True, comment="是否启用")
62
-
63
- # relationships
64
- material_lots: Mapped[list["MaterialLot"]] = relationship(back_populates="warehouse")
65
- ```
66
-
67
- ---
68
-
69
- ## 二、SQLite 特有注意事项
70
-
71
- | 事项 | 规范 |
72
- |----------------|------------------------------------------------------------|
73
- | WAL 模式 | 生产环境必须启用 `PRAGMA journal_mode=WAL` |
74
- | 外键约束 | 默认关闭,必须执行 `PRAGMA foreign_keys=ON` |
75
- | 类型亲和性 | 严格按模型定义列类型,避免隐式转换 |
76
- | 并发写入 | SQLite 不支持并发写,Celery worker 必须 `--concurrency=1` |
77
- | 文件权限 | 数据库文件目录需要读写权限 |
78
- | 备份 | 定期 VACUUM + 文件拷贝 |
79
-
80
- ### 引擎配置
81
-
82
- ```python
83
- # app/core/database.py
84
- from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
85
- from sqlalchemy import text
86
- from app.core.config import settings
87
-
88
- engine = create_async_engine(
89
- settings.DATABASE_URL,
90
- echo=settings.DEBUG,
91
- connect_args={"check_same_thread": False},
92
- )
93
-
94
- async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
95
-
96
- async def init_db():
97
- """初始化数据库(启用 WAL 模式)"""
98
- async with engine.connect() as conn:
99
- await conn.execute(text("PRAGMA journal_mode=WAL"))
100
- await conn.execute(text("PRAGMA foreign_keys=ON"))
101
- await conn.commit()
102
-
103
- async def get_db():
104
- """数据库会话依赖注入"""
105
- async with async_session() as session:
106
- try:
107
- yield session
108
- except Exception:
109
- await session.rollback()
110
- raise
111
- ```
112
-
113
- ---
114
-
115
- ## 三、Alembic 迁移规范
116
-
117
- ### 3.1 初始化
118
-
119
- ```bash
120
- alembic init alembic
121
- # 修改 alembic.ini 的 sqlalchemy.url
122
- # 修改 alembic/env.py 导入 Base.metadata
123
- ```
124
-
125
- ### 3.2 迁移命名
126
-
127
- ```bash
128
- alembic revision --autogenerate -m "add_warehouse_table"
129
- alembic revision --autogenerate -m "add_index_warehouse_code"
130
- ```
131
-
132
- ### 3.3 迁移规则
133
-
134
- | 规则 | 说明 |
135
- |------------|------------------------------------------------|
136
- | 自动生成 | 始终使用 `--autogenerate`,手动 review 后提交 |
137
- | 不可逆操作 | DROP TABLE/DROP COLUMN 前必须确认数据安全 |
138
- | 数据迁移 | 数据迁移和结构迁移分开为两个 revision |
139
- | 测试 | 迁移后必须运行测试验证 |
140
- | 提交 | 迁移文件必须纳入版本控制 |
141
-
142
- ---
143
-
144
- ## 四、查询规范
145
-
146
- ### 4.1 使用 select() 风格
147
-
148
- ```python
149
- from sqlalchemy import select, func
150
-
151
- # 单条查询
152
- stmt = select(Warehouse).where(Warehouse.id == warehouse_id)
153
- result = await db.execute(stmt)
154
- warehouse = result.scalar_one_or_none()
155
-
156
- # 分页查询
157
- stmt = (
158
- select(Warehouse)
159
- .where(Warehouse.is_deleted == False, Warehouse.is_active == True)
160
- .order_by(Warehouse.created_at.desc())
161
- .offset((page - 1) * page_size)
162
- .limit(page_size)
163
- )
164
- result = await db.execute(stmt)
165
- items = result.scalars().all()
166
-
167
- # 计数
168
- count_stmt = select(func.count()).select_from(Warehouse).where(Warehouse.is_deleted == False)
169
- total = (await db.execute(count_stmt)).scalar()
170
- ```
171
-
172
- ### 4.2 禁止事项
173
-
174
- - 禁止使用 `session.query()` 风格(SQLAlchemy 1.x 遗留)
175
- - 禁止字符串拼接 SQL
176
- - 禁止在循环中执行查询(N+1 问题)
177
- - 禁止不带 WHERE 的 UPDATE/DELETE
178
-
179
- ---
180
-
181
- ## 五、Repository 模式
182
-
183
- ```python
184
- # app/repositories/warehouse.py
185
- class WarehouseRepository:
186
- """仓库数据访问层"""
187
-
188
- async def get_by_id(self, db: AsyncSession, warehouse_id: int) -> Warehouse | None:
189
- return await db.get(Warehouse, warehouse_id)
190
-
191
- async def get_by_code(self, db: AsyncSession, code: str) -> Warehouse | None:
192
- stmt = select(Warehouse).where(Warehouse.code == code, Warehouse.is_deleted == False)
193
- result = await db.execute(stmt)
194
- return result.scalar_one_or_none()
195
-
196
- async def get_page(
197
- self, db: AsyncSession, page: int, page_size: int, filters: dict | None = None
198
- ) -> tuple[list[Warehouse], int]:
199
- base = select(Warehouse).where(Warehouse.is_deleted == False)
200
- if filters:
201
- if "name" in filters:
202
- base = base.where(Warehouse.name.contains(filters["name"]))
203
- if "is_active" in filters:
204
- base = base.where(Warehouse.is_active == filters["is_active"])
205
-
206
- count_stmt = select(func.count()).select_from(base.subquery())
207
- total = (await db.execute(count_stmt)).scalar()
208
-
209
- stmt = base.order_by(Warehouse.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
210
- result = await db.execute(stmt)
211
- return result.scalars().all(), total
212
-
213
- warehouse_repo = WarehouseRepository()
214
- ```
215
-
216
- ---
217
-
218
- ## 六、索引策略
219
-
220
- | 场景 | 索引类型 | 示例 |
221
- |----------------|------------------|-------------------------------------------|
222
- | 唯一约束 | Unique Index | code, email |
223
- | 高频查询条件 | 普通索引 | is_active, status |
224
- | 组合查询 | 复合索引 | (warehouse_id, material_type) |
225
- | 外键 | 自动创建 | relationship 自动生成 |
226
- | 软删除过滤 | 部分索引 | WHERE is_deleted = False |
1
+ # 数据库设计与使用规范
2
+
3
+ > **版本**: v1.0 | **修订日期**: 2026-07-15
4
+ > **技术栈**: SQLAlchemy 2.0+ / Alembic / SQLite (aiosqlite)
5
+
6
+ ---
7
+
8
+ ## 一、SQLAlchemy 基类
9
+
10
+ ### 1.1 声明式基类
11
+
12
+ ```python
13
+ # app/models/base.py
14
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
15
+ from sqlalchemy import Integer, String, Boolean, DateTime, func
16
+ from datetime import datetime
17
+
18
+ class Base(DeclarativeBase):
19
+ """SQLAlchemy 声明式基类"""
20
+ pass
21
+
22
+ class TimestampMixin:
23
+ """时间戳 Mixin — 自动记录创建/更新时间"""
24
+ created_at: Mapped[datetime] = mapped_column(
25
+ DateTime, server_default=func.now(), nullable=False
26
+ )
27
+ updated_at: Mapped[datetime] = mapped_column(
28
+ DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
29
+ )
30
+
31
+ class SoftDeleteMixin:
32
+ """软删除 Mixin"""
33
+ is_deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
34
+
35
+ class AuditMixin:
36
+ """审计 Mixin — 记录操作人"""
37
+ created_by: Mapped[int | None] = mapped_column(Integer, nullable=True)
38
+ updated_by: Mapped[int | None] = mapped_column(Integer, nullable=True)
39
+ ```
40
+
41
+ ### 1.2 模型定义示例
42
+
43
+ ```python
44
+ # app/models/warehouse.py
45
+ from sqlalchemy import String, Integer, Text, Index
46
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
47
+ from app.models.base import Base, TimestampMixin, SoftDeleteMixin, AuditMixin
48
+
49
+ class Warehouse(Base, TimestampMixin, SoftDeleteMixin, AuditMixin):
50
+ """仓库模型"""
51
+ __tablename__ = "warehouses"
52
+ __table_args__ = (
53
+ Index("ix_warehouse_code", "code", unique=True),
54
+ Index("ix_warehouse_active", "is_active"),
55
+ )
56
+
57
+ id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
58
+ code: Mapped[str] = mapped_column(String(50), nullable=False, comment="仓库编码")
59
+ name: Mapped[str] = mapped_column(String(200), nullable=False, comment="仓库名称")
60
+ location: Mapped[str | None] = mapped_column(String(500), comment="位置")
61
+ is_active: Mapped[bool] = mapped_column(default=True, comment="是否启用")
62
+
63
+ # relationships
64
+ material_lots: Mapped[list["MaterialLot"]] = relationship(back_populates="warehouse")
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 二、SQLite 特有注意事项
70
+
71
+ | 事项 | 规范 |
72
+ |----------------|------------------------------------------------------------|
73
+ | WAL 模式 | 生产环境必须启用 `PRAGMA journal_mode=WAL` |
74
+ | 外键约束 | 默认关闭,必须执行 `PRAGMA foreign_keys=ON` |
75
+ | 类型亲和性 | 严格按模型定义列类型,避免隐式转换 |
76
+ | 并发写入 | SQLite 不支持并发写,Celery worker 必须 `--concurrency=1` |
77
+ | 文件权限 | 数据库文件目录需要读写权限 |
78
+ | 备份 | 定期 VACUUM + 文件拷贝 |
79
+
80
+ ### 引擎配置
81
+
82
+ ```python
83
+ # app/core/database.py
84
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
85
+ from sqlalchemy import text
86
+ from app.core.config import settings
87
+
88
+ engine = create_async_engine(
89
+ settings.DATABASE_URL,
90
+ echo=settings.DEBUG,
91
+ connect_args={"check_same_thread": False},
92
+ )
93
+
94
+ async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
95
+
96
+ async def init_db():
97
+ """初始化数据库(启用 WAL 模式)"""
98
+ async with engine.connect() as conn:
99
+ await conn.execute(text("PRAGMA journal_mode=WAL"))
100
+ await conn.execute(text("PRAGMA foreign_keys=ON"))
101
+ await conn.commit()
102
+
103
+ async def get_db():
104
+ """数据库会话依赖注入"""
105
+ async with async_session() as session:
106
+ try:
107
+ yield session
108
+ except Exception:
109
+ await session.rollback()
110
+ raise
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 三、Alembic 迁移规范
116
+
117
+ ### 3.1 初始化
118
+
119
+ ```bash
120
+ alembic init alembic
121
+ # 修改 alembic.ini 的 sqlalchemy.url
122
+ # 修改 alembic/env.py 导入 Base.metadata
123
+ ```
124
+
125
+ ### 3.2 迁移命名
126
+
127
+ ```bash
128
+ alembic revision --autogenerate -m "add_warehouse_table"
129
+ alembic revision --autogenerate -m "add_index_warehouse_code"
130
+ ```
131
+
132
+ ### 3.3 迁移规则
133
+
134
+ | 规则 | 说明 |
135
+ |------------|------------------------------------------------|
136
+ | 自动生成 | 始终使用 `--autogenerate`,手动 review 后提交 |
137
+ | 不可逆操作 | DROP TABLE/DROP COLUMN 前必须确认数据安全 |
138
+ | 数据迁移 | 数据迁移和结构迁移分开为两个 revision |
139
+ | 测试 | 迁移后必须运行测试验证 |
140
+ | 提交 | 迁移文件必须纳入版本控制 |
141
+
142
+ ---
143
+
144
+ ## 四、查询规范
145
+
146
+ ### 4.1 使用 select() 风格
147
+
148
+ ```python
149
+ from sqlalchemy import select, func
150
+
151
+ # 单条查询
152
+ stmt = select(Warehouse).where(Warehouse.id == warehouse_id)
153
+ result = await db.execute(stmt)
154
+ warehouse = result.scalar_one_or_none()
155
+
156
+ # 分页查询
157
+ stmt = (
158
+ select(Warehouse)
159
+ .where(Warehouse.is_deleted == False, Warehouse.is_active == True)
160
+ .order_by(Warehouse.created_at.desc())
161
+ .offset((page - 1) * page_size)
162
+ .limit(page_size)
163
+ )
164
+ result = await db.execute(stmt)
165
+ items = result.scalars().all()
166
+
167
+ # 计数
168
+ count_stmt = select(func.count()).select_from(Warehouse).where(Warehouse.is_deleted == False)
169
+ total = (await db.execute(count_stmt)).scalar()
170
+ ```
171
+
172
+ ### 4.2 禁止事项
173
+
174
+ - 禁止使用 `session.query()` 风格(SQLAlchemy 1.x 遗留)
175
+ - 禁止字符串拼接 SQL
176
+ - 禁止在循环中执行查询(N+1 问题)
177
+ - 禁止不带 WHERE 的 UPDATE/DELETE
178
+
179
+ ---
180
+
181
+ ## 五、Repository 模式
182
+
183
+ ```python
184
+ # app/repositories/warehouse.py
185
+ class WarehouseRepository:
186
+ """仓库数据访问层"""
187
+
188
+ async def get_by_id(self, db: AsyncSession, warehouse_id: int) -> Warehouse | None:
189
+ return await db.get(Warehouse, warehouse_id)
190
+
191
+ async def get_by_code(self, db: AsyncSession, code: str) -> Warehouse | None:
192
+ stmt = select(Warehouse).where(Warehouse.code == code, Warehouse.is_deleted == False)
193
+ result = await db.execute(stmt)
194
+ return result.scalar_one_or_none()
195
+
196
+ async def get_page(
197
+ self, db: AsyncSession, page: int, page_size: int, filters: dict | None = None
198
+ ) -> tuple[list[Warehouse], int]:
199
+ base = select(Warehouse).where(Warehouse.is_deleted == False)
200
+ if filters:
201
+ if "name" in filters:
202
+ base = base.where(Warehouse.name.contains(filters["name"]))
203
+ if "is_active" in filters:
204
+ base = base.where(Warehouse.is_active == filters["is_active"])
205
+
206
+ count_stmt = select(func.count()).select_from(base.subquery())
207
+ total = (await db.execute(count_stmt)).scalar()
208
+
209
+ stmt = base.order_by(Warehouse.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
210
+ result = await db.execute(stmt)
211
+ return result.scalars().all(), total
212
+
213
+ warehouse_repo = WarehouseRepository()
214
+ ```
215
+
216
+ ---
217
+
218
+ ## 六、索引策略
219
+
220
+ | 场景 | 索引类型 | 示例 |
221
+ |----------------|------------------|-------------------------------------------|
222
+ | 唯一约束 | Unique Index | code, email |
223
+ | 高频查询条件 | 普通索引 | is_active, status |
224
+ | 组合查询 | 复合索引 | (warehouse_id, material_type) |
225
+ | 外键 | 自动创建 | relationship 自动生成 |
226
+ | 软删除过滤 | 部分索引 | WHERE is_deleted = False |