第一版提交,答题功能OK,题库管理待完善

This commit is contained in:
2025-12-18 19:07:21 +08:00
parent e5600535be
commit ba252b2f56
93 changed files with 20431 additions and 1 deletions

View File

@@ -0,0 +1,198 @@
domains:
- name: "User"
description: "参与答题的用户"
entities:
- name: "User"
description: "用户信息实体"
attributes:
- name: "id"
type: "string"
description: "用户唯一标识"
- name: "name"
type: "string"
description: "用户姓名2-20位中英文"
- name: "phone"
type: "string"
description: "手机号11位数字"
- name: "password"
type: "string"
description: "登录密码 (敏感字段,仅管理员可见;当前版本为明文存储)"
- name: "createdAt"
type: "datetime"
description: "创建时间"
- name: "Question"
description: "题库管理"
entities:
- name: "QuestionCategory"
description: "题目类别"
attributes:
- name: "id"
type: "string"
description: "类别唯一标识"
- name: "name"
type: "string"
description: "类别名称 (例如:通用/网络/数据库等)"
- name: "createdAt"
type: "datetime"
description: "创建时间"
- name: "Question"
description: "题目实体"
attributes:
- name: "id"
type: "string"
description: "题目唯一标识"
- name: "content"
type: "string"
description: "题目内容"
- name: "type"
type: "enum"
values: ["single", "multiple", "judgment", "text"]
description: "题型"
- name: "category"
type: "string"
description: "题目类别名称 (默认:通用)"
- name: "options"
type: "json"
description: "选项内容 (JSON格式)"
- name: "answer"
type: "string"
description: "标准答案"
- name: "score"
type: "integer"
description: "分值"
- name: "createdAt"
type: "datetime"
description: "创建时间"
- name: "Exam"
description: "考试科目与考试任务"
entities:
- name: "ExamSubject"
description: "考试科目 (一套出题规则)"
attributes:
- name: "id"
type: "string"
description: "科目唯一标识"
- name: "name"
type: "string"
description: "科目名称"
- name: "totalScore"
type: "integer"
description: "总分"
- name: "timeLimitMinutes"
type: "integer"
description: "答题时间限制(分钟)默认60"
- name: "typeRatios"
type: "json"
description: "题型比重 (single/multiple/judgment/text)"
- name: "categoryRatios"
type: "json"
description: "题目类别比重 (categoryName -> ratio)"
- name: "createdAt"
type: "datetime"
description: "创建时间"
- name: "updatedAt"
type: "datetime"
description: "更新时间"
- name: "ExamTask"
description: "考试任务 (给用户分派某个科目)"
attributes:
- name: "id"
type: "string"
description: "任务唯一标识"
- name: "name"
type: "string"
description: "任务名称"
- name: "subjectId"
type: "string"
description: "关联科目ID"
- name: "startAt"
type: "datetime"
description: "开始答题时间"
- name: "endAt"
type: "datetime"
description: "截止答题时间"
- name: "createdAt"
type: "datetime"
description: "创建时间"
- name: "ExamTaskUser"
description: "任务参与用户"
attributes:
- name: "id"
type: "string"
description: "关联唯一标识"
- name: "taskId"
type: "string"
description: "任务ID"
- name: "userId"
type: "string"
description: "用户ID"
- name: "assignedAt"
type: "datetime"
description: "分派时间"
- name: "Quiz"
description: "答题业务"
entities:
- name: "QuizRecord"
description: "答题记录"
attributes:
- name: "id"
type: "string"
description: "记录唯一标识"
- name: "userId"
type: "string"
description: "用户ID"
- name: "totalScore"
type: "integer"
description: "总得分"
- name: "correctCount"
type: "integer"
description: "正确题数"
- name: "totalCount"
type: "integer"
description: "总题数"
- name: "createdAt"
type: "datetime"
description: "答题时间"
- name: "QuizAnswer"
description: "单题答题详情"
attributes:
- name: "id"
type: "string"
description: "答案唯一标识"
- name: "recordId"
type: "string"
description: "关联的答题记录ID"
- name: "questionId"
type: "string"
description: "题目ID"
- name: "userAnswer"
type: "string"
description: "用户提交的答案"
- name: "isCorrect"
type: "boolean"
description: "是否正确"
- name: "score"
type: "integer"
description: "该题得分"
- name: "System"
description: "系统配置"
entities:
- name: "SystemConfig"
description: "系统全局配置"
attributes:
- name: "id"
type: "string"
description: "配置唯一标识"
- name: "configType"
type: "string"
description: "配置类型键"
- name: "configValue"
type: "string"
description: "配置值 (通常为JSON字符串)"
- name: "updatedAt"
type: "datetime"
description: "更新时间"

648
.opencode/200-api/api.yaml Normal file
View File

@@ -0,0 +1,648 @@
openapi: "3.0.0"
info:
title: "Survey System API"
version: "1.1.0"
components:
securitySchemes:
AdminBearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
User:
type: object
required: ["id", "name", "phone", "createdAt"]
properties:
id:
type: string
name:
type: string
phone:
type: string
createdAt:
type: string
format: date-time
AdminUserView:
type: object
required: ["id", "name", "phone", "createdAt", "password"]
properties:
id:
type: string
name:
type: string
phone:
type: string
createdAt:
type: string
format: date-time
password:
type: string
description: "密码 (敏感字段;前端掩码显示)"
QuestionCategory:
type: object
required: ["id", "name", "createdAt"]
properties:
id:
type: string
name:
type: string
createdAt:
type: string
format: date-time
Question:
type: object
required: ["content", "type", "answer", "score"]
properties:
content:
type: string
type:
type: string
enum: ["single", "multiple", "judgment", "text"]
category:
type: string
description: "题目类别名称,缺省为通用"
default: "通用"
options:
type: array
items:
type: string
answer:
oneOf:
- type: string
- type: array
items:
type: string
score:
type: number
ExamSubject:
type: object
required: ["id", "name", "totalScore", "timeLimitMinutes", "typeRatios", "categoryRatios"]
properties:
id:
type: string
name:
type: string
totalScore:
type: integer
timeLimitMinutes:
type: integer
default: 60
typeRatios:
type: object
additionalProperties: false
properties:
single:
type: number
multiple:
type: number
judgment:
type: number
text:
type: number
categoryRatios:
type: object
additionalProperties:
type: number
ExamTask:
type: object
required: ["id", "name", "subjectId", "startAt", "endAt"]
properties:
id:
type: string
name:
type: string
subjectId:
type: string
startAt:
type: string
format: date-time
endAt:
type: string
format: date-time
Pagination:
type: object
required: ["page", "limit", "total", "pages"]
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
pages:
type: integer
paths:
/api/users:
post:
summary: "创建用户或登录"
requestBody:
required: true
content:
application/json:
schema:
type: object
required: ["name", "phone", "password"]
properties:
name:
type: string
description: "用户姓名2-20位中英文"
phone:
type: string
description: "手机号"
password:
type: string
description: "登录密码"
responses:
"200":
description: "User created"
/api/users/{id}:
get:
summary: "获取用户信息"
parameters:
- name: "id"
in: "path"
required: true
schema:
type: string
responses:
"200":
description: "User details"
/api/questions/import:
post:
summary: "Excel导入题目"
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
responses:
"200":
description: "Import result"
/api/questions:
get:
summary: "获取题目列表"
parameters:
- name: "type"
in: query
schema:
type: string
enum: ["single", "multiple", "judgment", "text"]
- name: "category"
in: query
schema:
type: string
- name: "page"
in: query
schema:
type: integer
- name: "limit"
in: query
schema:
type: integer
responses:
"200":
description: "List of questions"
post:
summary: "添加单题"
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Question"
responses:
"200":
description: "Question created"
/api/questions/{id}:
put:
summary: "更新题目"
parameters:
- name: "id"
in: "path"
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Question"
responses:
"200":
description: "Question updated"
delete:
summary: "删除题目"
parameters:
- name: "id"
in: "path"
required: true
schema:
type: string
responses:
"200":
description: "Question deleted"
/api/quiz/generate:
post:
summary: "生成随机试卷"
requestBody:
content:
application/json:
schema:
type: object
required: ["userId", "subjectId"]
properties:
userId:
type: string
subjectId:
type: string
description: "考试科目ID"
responses:
"200":
description: "Generated quiz"
/api/quiz/submit:
post:
summary: "提交答题"
requestBody:
content:
application/json:
schema:
type: object
required: ["userId", "answers"]
properties:
userId:
type: string
answers:
type: array
items:
type: object
responses:
"200":
description: "Submission result"
/api/quiz/records/{userId}:
get:
summary: "获取用户答题记录"
parameters:
- name: "userId"
in: "path"
required: true
schema:
type: string
responses:
"200":
description: "User quiz records"
/api/quiz/records/detail/{recordId}:
get:
summary: "获取答题记录详情"
parameters:
- name: "recordId"
in: path
required: true
schema:
type: string
responses:
"200":
description: "Record detail"
/api/admin/login:
post:
summary: "管理员登录"
requestBody:
content:
application/json:
schema:
type: object
required: ["username", "password"]
properties:
username:
type: string
password:
type: string
responses:
"200":
description: "Login success"
/api/admin/statistics:
get:
summary: "获取统计数据"
responses:
"200":
description: "Statistics data"
/api/admin/users:
get:
summary: "获取用户列表 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: "page"
in: query
schema:
type: integer
- name: "limit"
in: query
schema:
type: integer
responses:
"200":
description: "User list"
delete:
summary: "删除用户 (管理员)"
security:
- AdminBearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: ["userId"]
properties:
userId:
type: string
responses:
"200":
description: "Deleted"
/api/admin/users/export:
get:
summary: "导出用户 (管理员)"
security:
- AdminBearerAuth: []
responses:
"200":
description: "Export users"
/api/admin/users/import:
post:
summary: "导入用户 (管理员)"
security:
- AdminBearerAuth: []
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
responses:
"200":
description: "Import users"
/api/admin/users/{userId}/records:
get:
summary: "获取用户历史答题记录 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: userId
in: path
required: true
schema:
type: string
responses:
"200":
description: "User records"
/api/admin/question-categories:
get:
summary: "获取题目类别列表 (管理员)"
security:
- AdminBearerAuth: []
responses:
"200":
description: "Category list"
post:
summary: "新增题目类别 (管理员)"
security:
- AdminBearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: ["name"]
properties:
name:
type: string
responses:
"200":
description: "Created"
/api/admin/question-categories/{id}:
put:
summary: "更新题目类别 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required: ["name"]
properties:
name:
type: string
responses:
"200":
description: "Updated"
delete:
summary: "删除题目类别 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: "Deleted"
/api/admin/subjects:
get:
summary: "获取考试科目列表 (管理员)"
security:
- AdminBearerAuth: []
responses:
"200":
description: "Subjects"
post:
summary: "新增考试科目 (管理员)"
security:
- AdminBearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ExamSubject"
responses:
"200":
description: "Created"
/api/admin/subjects/{id}:
put:
summary: "更新考试科目 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ExamSubject"
responses:
"200":
description: "Updated"
delete:
summary: "删除考试科目 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: "Deleted"
/api/subjects:
get:
summary: "获取考试科目列表 (用户)"
responses:
"200":
description: "Subjects"
/api/admin/tasks:
get:
summary: "获取考试任务列表 (管理员)"
security:
- AdminBearerAuth: []
responses:
"200":
description: "Tasks"
post:
summary: "新增考试任务并分派用户 (管理员)"
security:
- AdminBearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: ["name", "subjectId", "startAt", "endAt", "userIds"]
properties:
name:
type: string
subjectId:
type: string
startAt:
type: string
format: date-time
endAt:
type: string
format: date-time
userIds:
type: array
items:
type: string
responses:
"200":
description: "Created"
/api/admin/tasks/{id}:
put:
summary: "更新考试任务 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: "Updated"
delete:
summary: "删除考试任务 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: "Deleted"
/api/admin/tasks/{id}/report:
get:
summary: "导出任务报表 (管理员)"
security:
- AdminBearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: "Task report"
/api/admin/config:
get:
summary: "获取抽题配置"
responses:
"200":
description: "Quiz config"
put:
summary: "更新抽题配置"
requestBody:
content:
application/json:
schema:
type: object
responses:
"200":
description: "Config updated"

View File

@@ -0,0 +1,256 @@
database:
type: "sqlite"
version: "3"
tables:
- name: "users"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "name"
type: "TEXT"
nullable: false
constraints: "CHECK(length(name) >= 2 AND length(name) <= 20)"
- name: "phone"
type: "TEXT"
nullable: false
unique: true
constraints: "CHECK(length(phone) = 11 AND phone LIKE '1%' AND substr(phone, 2, 1) BETWEEN '3' AND '9')"
- name: "password"
type: "TEXT"
comment: "用户登录密码 (当前版本为明文存储)"
- name: "created_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_users_phone"
columns: ["phone"]
- name: "idx_users_created_at"
columns: ["created_at"]
- name: "question_categories"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "name"
type: "TEXT"
nullable: false
unique: true
- name: "created_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_question_categories_name"
columns: ["name"]
- name: "questions"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "content"
type: "TEXT"
nullable: false
- name: "type"
type: "TEXT"
nullable: false
constraints: "CHECK(type IN ('single', 'multiple', 'judgment', 'text'))"
- name: "category"
type: "TEXT"
nullable: false
default: "'通用'"
comment: "题目类别名称 (无类别时默认通用)"
- name: "options"
type: "TEXT"
comment: "JSON格式存储选项"
- name: "answer"
type: "TEXT"
nullable: false
- name: "score"
type: "INTEGER"
nullable: false
constraints: "CHECK(score > 0)"
- name: "created_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_questions_type"
columns: ["type"]
- name: "idx_questions_category"
columns: ["category"]
- name: "idx_questions_score"
columns: ["score"]
- name: "exam_subjects"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "name"
type: "TEXT"
nullable: false
unique: true
- name: "total_score"
type: "INTEGER"
nullable: false
constraints: "CHECK(total_score > 0)"
- name: "time_limit_minutes"
type: "INTEGER"
nullable: false
default: "60"
constraints: "CHECK(time_limit_minutes > 0)"
- name: "type_ratios"
type: "TEXT"
nullable: false
comment: "题型比重 JSON (single/multiple/judgment/text)"
- name: "category_ratios"
type: "TEXT"
nullable: false
comment: "题目类别比重 JSON (categoryName->ratio)"
- name: "created_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
- name: "updated_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_exam_subjects_name"
columns: ["name"]
- name: "exam_tasks"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "name"
type: "TEXT"
nullable: false
- name: "subject_id"
type: "TEXT"
nullable: false
foreignKey:
table: "exam_subjects"
column: "id"
- name: "start_at"
type: "DATETIME"
nullable: false
- name: "end_at"
type: "DATETIME"
nullable: false
- name: "created_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_exam_tasks_subject_id"
columns: ["subject_id"]
- name: "idx_exam_tasks_start_at"
columns: ["start_at"]
- name: "idx_exam_tasks_end_at"
columns: ["end_at"]
- name: "exam_task_users"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "task_id"
type: "TEXT"
nullable: false
foreignKey:
table: "exam_tasks"
column: "id"
- name: "user_id"
type: "TEXT"
nullable: false
foreignKey:
table: "users"
column: "id"
- name: "assigned_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_exam_task_users_task_id"
columns: ["task_id"]
- name: "idx_exam_task_users_user_id"
columns: ["user_id"]
- name: "quiz_records"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "user_id"
type: "TEXT"
nullable: false
foreignKey:
table: "users"
column: "id"
- name: "total_score"
type: "INTEGER"
nullable: false
- name: "correct_count"
type: "INTEGER"
nullable: false
- name: "total_count"
type: "INTEGER"
nullable: false
- name: "created_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_quiz_records_user_id"
columns: ["user_id"]
- name: "idx_quiz_records_created_at"
columns: ["created_at"]
- name: "quiz_answers"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "record_id"
type: "TEXT"
nullable: false
foreignKey:
table: "quiz_records"
column: "id"
- name: "question_id"
type: "TEXT"
nullable: false
foreignKey:
table: "questions"
column: "id"
- name: "user_answer"
type: "TEXT"
nullable: false
- name: "score"
type: "INTEGER"
nullable: false
- name: "is_correct"
type: "BOOLEAN"
nullable: false
- name: "created_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"
indexes:
- name: "idx_quiz_answers_record_id"
columns: ["record_id"]
- name: "idx_quiz_answers_question_id"
columns: ["question_id"]
- name: "system_configs"
columns:
- name: "id"
type: "TEXT"
primaryKey: true
- name: "config_type"
type: "TEXT"
nullable: false
unique: true
- name: "config_value"
type: "TEXT"
nullable: false
- name: "updated_at"
type: "DATETIME"
default: "CURRENT_TIMESTAMP"

View File

@@ -0,0 +1,25 @@
---
agent: build
description: Implement an approved OpenSpec change and keep tasks in sync.
---
The user has requested to implement the following change proposal. Find the change proposal and follow the instructions below. If you're not sure or if ambiguous, ask for clarification from the user.
<UserRequest>
$ARGUMENTS
</UserRequest>
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
**Steps**
Track these steps as TODOs and complete them one by one.
1. Read `changes/<id>/proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria.
2. Work through tasks sequentially, keeping edits minimal and focused on the requested change.
3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished.
4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality.
5. Reference `openspec list` or `openspec show <item>` when additional context is required.
**Reference**
- Use `openspec show <id> --json --deltas-only` if you need additional context from the proposal while implementing.
<!-- OPENSPEC:END -->

View File

@@ -0,0 +1,28 @@
---
agent: build
description: Archive a deployed OpenSpec change and update specs.
---
<ChangeId>
$ARGUMENTS
</ChangeId>
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
**Steps**
1. Determine the change ID to archive:
- If this prompt already includes a specific change ID (for example inside a `<ChangeId>` block populated by slash-command arguments), use that value after trimming whitespace.
- If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
- Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
- If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
2. Validate the change ID by running `openspec list` (or `openspec show <id>`) and stop if the change is missing, already archived, or otherwise not ready to archive.
3. Run `openspec archive <id> --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work).
4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`.
5. Validate with `openspec validate --strict` and inspect with `openspec show <id>` if anything looks off.
**Reference**
- Use `openspec list` to confirm change IDs before archiving.
- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off.
<!-- OPENSPEC:END -->

View File

@@ -0,0 +1,29 @@
---
agent: build
description: Scaffold a new OpenSpec change and validate strictly.
---
The user has requested the following change proposal. Use the openspec instructions to create their change proposal.
<UserRequest>
$ARGUMENTS
</UserRequest>
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files.
**Steps**
1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification.
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes/<id>/`.
3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
5. Draft spec deltas in `changes/<id>/specs/<capability>/spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant.
6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
7. Validate with `openspec validate <id> --strict` and resolve every issue before sharing the proposal.
**Reference**
- Use `openspec show <id> --json --deltas-only` or `openspec show <spec> --type spec` to inspect details when validation fails.
- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones.
- Explore the codebase with `rg <keyword>`, `ls`, or direct file reads so proposals align with current implementation realities.
<!-- OPENSPEC:END -->

14
.opencode/manifest.yaml Normal file
View File

@@ -0,0 +1,14 @@
specVersion: "0.1.0"
info:
title: "问卷调查系统 (Survey System)"
description: "一个功能完善的在线考试/问卷调查平台,支持多种题型、随机抽题、免注册答题。"
version: "1.1.0"
license:
name: "Proprietary"
references:
- path: "./100-business/domain.yaml"
type: "domain"
- path: "./200-api/api.yaml"
type: "api"
- path: "./300-database/schema.yaml"
type: "database"