From 2594652cc31bab30ed713232d856fe512d79de5f Mon Sep 17 00:00:00 2001 From: MerCry Date: Tue, 24 Feb 2026 12:08:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/pr-check.yaml | 108 ++++++++++++++ agents.md | 50 +++++++ docs/progress/example-progress.md | 105 ++++++++++++++ docs/session-handoff-protocol.md | 118 +++++++++++++++ docs/spec-product-zh.md | 234 ++++++++++++++++++++++++++++++ scripts/check-openapi-diff.sh | 77 ++++++++++ scripts/check-openapi-level.sh | 98 +++++++++++++ scripts/check-traceability.sh | 55 +++++++ spec/contracting.md | 65 +++++++++ 9 files changed, 910 insertions(+) create mode 100644 .gitea/workflows/pr-check.yaml create mode 100644 agents.md create mode 100644 docs/progress/example-progress.md create mode 100644 docs/session-handoff-protocol.md create mode 100644 docs/spec-product-zh.md create mode 100644 scripts/check-openapi-diff.sh create mode 100644 scripts/check-openapi-level.sh create mode 100644 scripts/check-traceability.sh create mode 100644 spec/contracting.md diff --git a/.gitea/workflows/pr-check.yaml b/.gitea/workflows/pr-check.yaml new file mode 100644 index 0000000..52d673f --- /dev/null +++ b/.gitea/workflows/pr-check.yaml @@ -0,0 +1,108 @@ +name: PR Check (SDD Full Gate) + +on: + pull_request: + branches: [ main ] + paths: + - '.gitea/workflows/**' + - 'scripts/**' + - 'spec/**' + - 'src/**' + - 'test/**' + +jobs: + sdd-full-gate: + runs-on: ubuntu-latest + steps: + - name: Checkout code (no GitHub dependency) + shell: sh + run: | + set -eu + SERVER_URL="${GITHUB_SERVER_URL:-${GITEA_SERVER_URL:-}}" + REPO_NAME="${GITHUB_REPOSITORY:-${GITEA_REPOSITORY:-}}" + COMMIT_SHA="${GITHUB_SHA:-${GITEA_SHA:-}}" + + : "${SERVER_URL:?Could not determine SERVER_URL}" + : "${REPO_NAME:?Could not determine REPO_NAME}" + : "${COMMIT_SHA:?Could not determine COMMIT_SHA}" + + echo "Using SERVER_URL=$SERVER_URL" + echo "Using REPO_NAME=$REPO_NAME" + echo "Using COMMIT_SHA=$COMMIT_SHA" + + if [ -d ".git" ]; then + echo "Repo already initialized in workspace; using fetch" + git remote set-url origin "$SERVER_URL/$REPO_NAME.git" + else + git clone "$SERVER_URL/$REPO_NAME.git" . + fi + + # 关键:不要把 main fetch 到本地分支 main(会冲突) + git fetch origin main:refs/remotes/origin/main + git fetch --depth=1 origin "$COMMIT_SHA" + git checkout -f "$COMMIT_SHA" + + - name: 1. Commit Message Check + shell: sh + run: | + echo "Checking commit messages for [AC-...] or [TASK-...] (range: refs/remotes/origin/main..HEAD)" + # refs/remotes/origin/main is fetched in the checkout step + git log --no-merges --format=%B refs/remotes/origin/main..HEAD | cat + + if git log --no-merges --format=%B refs/remotes/origin/main..HEAD | grep -Eq '\[(AC|TASK)-'; then + echo "OK: Found [AC-...] or [TASK-...] in PR commits" + else + echo "ERROR: At least one commit message in the PR must contain [AC-...] or [TASK-...]" + exit 1 + fi + + - name: 2. OpenAPI Contract Level Check + env: + REQUIRE_PROVIDER_L2: "1" + shell: sh + run: | + chmod +x scripts/*.sh + ./scripts/check-openapi-level.sh + + - name: 3. AC Traceability Check + shell: sh + run: ./scripts/check-traceability.sh + + - name: 4. OpenAPI Breaking Change Check + shell: sh + run: ./scripts/check-openapi-diff.sh + + - name: 5. Minimum Self-Test (mvn test) + shell: sh + run: | + # 针对 Java Spring 项目运行最小单测 (方案 B: 不存在则提示跳过) + if command -v mvn >/dev/null 2>&1; then + # 处理本地 jar 依赖:如果 lib 目录下存在 jar 包,先安装到本地仓库 + if [ -f "lib/commons-codec-1.9.jar" ]; then + echo "Installing local jar: lib/commons-codec-1.9.jar" + mvn -q install:install-file \ + -Dfile=lib/commons-codec-1.9.jar \ + -DgroupId=commons-codec \ + -DartifactId=commons-codec \ + -Dversion=1.9 \ + -Dpackaging=jar \ + -DgeneratePom=true + fi + + mvn -q -DskipTests=false test + else + echo "Warning: mvn not found, skipping unit tests. Please ensure Runner has JDK/Maven for full enforcement." + fi + + - name: YAML Parse Check (Optional) + shell: sh + run: | + if command -v python3 >/dev/null 2>&1; then + if ! python3 -c "import yaml" 2>/dev/null; then + python3 -m pip install pyyaml --user >/dev/null 2>&1 || true + fi + if python3 -c "import yaml" 2>/dev/null; then + find spec -name "*.yaml" -o -name "*.yml" | xargs -I {} python3 -c "import yaml; yaml.safe_load(open('{}'))" + echo "YAML check passed." + fi + fi diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..b1b32f6 --- /dev/null +++ b/agents.md @@ -0,0 +1,50 @@ +# agents.md(ai 编码硬规则,必须遵守) + +## 0. 开始编码前(必须) +- 必须已读取(路径格式:`spec//...`): + - `spec/contracting.md`(契约硬规则) + - `spec//requirements.md`(当前模块需求) + - `spec//openapi.provider.yaml`(本模块提供) + - `spec//openapi.deps.yaml`(本模块依赖,如存在) +- **长会话/复杂任务接续**:若当前任务满足 `docs/session-handoff-protocol.md` 中的触发条件,**必须**先读取并持续更新 `docs/progress/{module}-{feature}-progress.md`。 +- 若上述任一文档缺失、冲突或内容不明确: + - **禁止开始实现** + - 必须在 `spec//tasks.md` 记录“待澄清”并停止 + +## 1. 提交与同步(Git Cadence,必须) +- **提交粒度**: + - `spec//` 下的规范文件变更必须**单独 commit**(不得与实现代码混在同一 commit)。 + - 实现代码按 `spec//tasks.md` 的**子任务完成**为粒度提交。 +- **提交触发点**(满足任一且必须通过“最小自测”): + - 任何 `spec//` 规范文件发生变更。 + - 任一 `spec//tasks.md` 子任务完成(从 ⏳/🔄 → ✅)。 + - 触发 `docs/session-handoff-protocol.md` 阈值并准备会话接续前。 +- **最小自测(必须)**: + - 能编译/构建通过。 + - 单元测试通过。 + - 至少一条契约校验或接口冒烟通过(与本次变更相关)。 +- **提交质量(必须)**: + - **严禁**提交编译不通过或未通过最小自测的代码(不允许 checkpoint commit)。 + - commit message 必须包含关联的验收标准 ID:`feat/fix: [AC-...]`。 + +## 2. 防需求偏移(必须) +- 未更新 `spec//requirements.md` 前,**禁止**改变业务规则、验收口径或用户可见行为。 +- 代码/测试/提交必须可追溯到验收标准: + - controller/endpoint 注释(或 `@Operation` 描述)必须包含 `[AC-...]` + - 测试类名或测试用例名必须包含 `[AC-...]` + - commit message 必须包含 `[AC-...]` + +## 2. 接口契约与成熟度(必须,细则见 `spec/contracting.md`) +- OpenAPI 文件必须声明全局成熟度:`info.x-contract-level: L0|L1|L2|L3`。 +- **Provider 合并门槛**:`openapi.provider.yaml` **< L2** 时,禁止将实现代码自动合并到 main。 +- **Consumer 并行规则**:允许在 feature 分支基于 `openapi.deps.yaml` 的 **L0/L1** 级别进行并行开发(Mock/SDK/页面流),无需等待提供方实现。 + +## 3. 自动合并门槛(全通过才允许) +- 单元测试通过。 +- 契约测试通过(Provider 响应符合 OpenAPI Schema,满足 L2 要求)。 +- OpenAPI Diff 检查通过(无未声明 breaking change)。 +- 需求追踪检查通过(AC 引用未断裂,且符合 `spec/contracting.md` 的自检清单)。 + +## 4. 分支与提交规范 +- 分支:`feature/-desc` 或 `fix/-desc` +- commit:`feat/fix: [AC-ID]` diff --git a/docs/progress/example-progress.md b/docs/progress/example-progress.md new file mode 100644 index 0000000..e8ad158 --- /dev/null +++ b/docs/progress/example-progress.md @@ -0,0 +1,105 @@ +# {module}-{feature} - Progress (Example) + +> 示例文件:用于演示 `docs/session-handoff-protocol.md` 的进度文档结构。 +> 复制此文件并重命名为:`docs/progress/{module}-{feature}-progress.md` + +--- + +## 📋 Context + +- module: `ruoyi-forum` +- feature: `REG` (example) +- status: 🔄 进行中 + +--- + +## 🔗 Spec References (SSOT) + +- agents: `agents.md` +- contracting: `spec/contracting.md` +- requirements: `spec/ruoyi-forum/requirements.md` +- openapi_provider: `spec/ruoyi-forum/openapi.provider.yaml` +- openapi_deps: `spec/ruoyi-forum/openapi.deps.yaml` +- design: `spec/ruoyi-forum/design.md` +- tasks: `spec/ruoyi-forum/tasks.md` + +> 注意:以上路径必须与实际模块目录一致;如果文件不存在,先创建/补齐 spec,再编码。 + +--- + +## 📊 Overall Progress (Phases) + +- [ ] Phase 1: Spec 初始化与对齐 (60%) 🔄 [tasks.md: T-INIT] +- [ ] Phase 2: Provider 实现与契约校验 (0%) ⏳ [tasks.md: T-PROVIDER] +- [ ] Phase 3: Consumer 并行开发(Mock/SDK) (0%) ⏳ [tasks.md: T-CONSUMER] +- [ ] Phase 4: 集成测试 / 回归 / 合并门禁 (0%) ⏳ [tasks.md: T-INTEGRATION] + +--- + +## 🔄 Current Phase + +### Goal +在 `spec/ruoyi-forum/` 下完成 requirements + openapi(provider/deps) 的最小可并行版本。 + +### Sub Tasks +- [x] 梳理模块边界与依赖清单 ✅ [tasks.md: T-INIT-01] +- [ ] 生成 `requirements.md` 草案 🔄 [tasks.md: T-INIT-02] +- [ ] 生成 `openapi.provider.yaml` 与 `openapi.deps.yaml` (L0) ⏳ [tasks.md: T-INIT-03] + +### Next Action (Must be Specific) + +**Immediate**: 生成 `spec/ruoyi-forum/requirements.md` 的验收标准(AC)与 Traceability 映射表。 + +**Details**: +1. file: `spec/ruoyi-forum/requirements.md` +2. action: 补齐至少 3 条 AC(EARS),并在 Traceability 表中映射到计划中的 endpoint。 +3. reference: + - `docs/spec-product-zh.md`(requirements 模板与 traceability 要求) + - `agents.md`(必须先读 spec 再编码、AC 追踪硬规则) +4. constraints: + - 不得在未更新 requirements 的情况下更改业务口径 + - AC ID 必须稳定可追溯(示例:`AC-REG-01`) + +--- + +## 🏗️ Technical Context + +### Module Structure (Only What Matters) + +- `spec/ruoyi-forum/` + - `requirements.md` + - `openapi.provider.yaml` + - `openapi.deps.yaml` + - `design.md` + - `tasks.md` + +### Key Decisions (Why / Impact) + +- decision: OpenAPI 拆分 provider/deps + reason: 支持 consumer-first 并行开发;调用方可基于 deps 生成 mock/SDK + impact: provider 实现需在合并前提升 provider 契约到 L2 并通过契约校验 + +### Code Snippets (Optional but Recommended) + +```text +// 示例:在 controller / @Operation / 测试用例 / commit 中包含 [AC-...] 以便追踪 +// [AC-REG-01] +``` + +--- + +## 🧾 Session History + +### Session #1 (YYYY-MM-DD) +- completed: + - 初始化 Phase 划分与任务清单 +- changes: + - 新增 docs/progress/example-progress.md + +--- + +## 🚀 Startup Guide + +1. 读取本进度文档,定位当前 Phase 与 Next Action。 +2. 打开并阅读 Spec References 指向的模块规范(requirements/openapi/tasks)。 +3. 直接执行 Next Action;遇到缺口先更新 spec 再编码。 diff --git a/docs/session-handoff-protocol.md b/docs/session-handoff-protocol.md new file mode 100644 index 0000000..1815aad --- /dev/null +++ b/docs/session-handoff-protocol.md @@ -0,0 +1,118 @@ +# Session Handoff Protocol - AI Reference v3.0 (Spec-Driven Aligned) + +> 面向“规范驱动 + 接口先行”体系的会话接续协议。 +> 核心目标:在多窗口、长任务场景下,通过单文档(Progress)实现 80% 上下文恢复。 + +--- + +## 🎯 核心目标 +跨会话任务延续 = 读取 Progress 文档 + 引用模块 Spec 目录 = 立即开始工作。 + +--- + +## ⚡ 触发条件(硬门禁阈值) + +当满足以下 **任一** 条件时,AI **必须** 启用本协议并维护进度文档: + +| 维度 | 触发阈值(必须启用) | 目的 | +| :--- | :--- | :--- | +| **子任务数** | `spec//tasks.md` 中子任务 ≥ 5 个 | 避免长列表任务丢失状态 | +| **文件变更** | 预计或已修改文件数 ≥ 10 个 | 追踪改动范围,防止遗漏 | +| **工具调用** | 单个会话内工具调用次数 ≥ 40 次 | 应对上下文堆积导致的智能下降 | +| **上下文质量** | 用户反馈或 AI 自检发现明显的指令偏移 | 强制重置上下文并对齐 SSOT | + +--- + +## 📋 进度文档强制清单(YAML 格式) + +进度文档路径:`docs/progress/{module}-{feature}-progress.md` + +```yaml +# 进度文档必须包含的字段(不可缺失) + +context: + module: "{module_name}" # 对应 spec// 目录名 + feature: "{feature_id}" # 对应 requirements.md 中的 feature_id + status: [🔄进行中 | ⏳待开始 | ✅已完成] + +spec_references: + # 必须引用模块 Spec 目录下的 SSOT 文档 + requirements: "spec/{module}/requirements.md" + openapi_provider: "spec/{module}/openapi.provider.yaml" + openapi_deps: "spec/{module}/openapi.deps.yaml" + design: "spec/{module}/design.md" + tasks: "spec/{module}/tasks.md" + +overall_progress: + format: "- [ ] Phase X: 名称 (进度%) [关联 Tasks.md ID]" + min_phases: 3 + max_phases: 6 + +current_phase: + goal: "一句话描述本阶段目标" + sub_tasks: + - [x] 子任务 A (✅) [Task ID] + - [ ] 子任务 B (🔄) [Task ID] + + next_action: + immediate: "下一步立即执行的具体原子任务" + details: + file: "path/to/file.ext:line_number" + action: "具体做什么(如:实现 XXX 接口的校验逻辑)" + reference: "参考文件及行号" + constraints: "执行此任务必须注意的硬约束(如:必须符合 AC-REG-01)" + +technical_context: + module_structure: "本次任务涉及的核心目录/文件" + key_decisions: + - decision: "技术决策内容" + reason: "为什么这么做" + impact: "对后续任务的影响" + code_snippets: "关键实现代码或 Mock 示例(至少 1-2 个)" + +session_history: + - session: "Session #X (YYYY-MM-DD)" + completed: [任务列表] + changes: [改动的文件清单] + +startup_guide: + - "Step 1: 读取本进度文档(了解当前位置与下一步)" + - "Step 2: 读取 spec_references 中定义的模块规范(了解业务与接口约束)" + - "Step 3: 直接执行 next_action" +``` + +--- + +## 🚀 工作规则(CRITICAL) + +### 1. 启动模式 +- **继续模式**:检查 `docs/progress/` 下是否存在对应模块的进度文档。若存在,Read 文档 → 引用 Spec 目录 → 简短汇报状态 → 直接开始。 +- **新建模式**:若满足“触发条件(硬门禁阈值)”任一项(或涉及跨模块并行),必须先创建进度文档,再开始工作。 + +### 2. 下一步行动(The Gold Rule) +- 禁止输出模糊的下一步(如“继续开发”)。 +- **必须**具体到 `文件路径:行号` 和 `原子动作`。 + +### 3. 停止与交接 +- 触发条件:Phase 完成、达到稳定检查点、或工具调用次数过多需同步。 +- **动作**:更新进度文档(标记 ✅、更新 🔄、详细记录 `next_action`)、告知用户如何接续。 + +### 4. 禁止事项 +- 禁止编造或假设需求;信息不足必须询问用户,并在 Progress 中记录澄清结果。 +- 禁止使用不存在的工具接口名;所有操作应基于当前环境可用工具。 + +--- + +## 🎨 响应模板 + +### 会话结束(产出交接) +```text +已达到阶段性检查点,进度文档已更新: +- 路径: docs/progress/{module}-{feature}-progress.md + +当前进度: +✅ {已完成任务} +🔄 当前停留: {具体文件:行号} + +下次接续请指令:"继续 {module} 的 {feature} 任务" +``` diff --git a/docs/spec-product-zh.md b/docs/spec-product-zh.md new file mode 100644 index 0000000..f5b99c7 --- /dev/null +++ b/docs/spec-product-zh.md @@ -0,0 +1,234 @@ +--- +name: spec-product-zh +version: 1.3.0 +description: "基于 Kiro Spec-Driven Development 与 API-First 思想的‘规范驱动+接口先行’协同开发方法论。通过结构化文档(Requirements/OpenAPI/Design/Tasks)实现 AI 开发的全链路可控与高效协作。" +license: MIT +compatibility: opencode +metadata: + audience: "开发者, 架构师, AI 智能体" + workflow: specification + language: zh-CN + methodology: + - "Kiro Spec-Driven Development" + - "API-First (接口先行)" + artifacts: + - requirements.md + - openapi.provider.yaml + - openapi.deps.yaml + - design.md + - tasks.md +--- + +# 规范驱动 + 接口先行(Spec-Driven + API-First)开发方法论 + +本方法论旨在通过结构化规范文档作为人类与 AI 的“共同语言”,并以接口契约作为协作锚点,实现多角色 AI 的并行、有序、高质量开发。 + +> 职责边界: +> - 本文档专注于**生成规范文档**(requirements/design/tasks/openapi)。 +> - AI 在编码阶段的行为准则、门禁、引用规则等执行规范:遵循项目根目录 `agents.md`。 +> - 契约成熟度与门禁的硬规则(给 AI):见 `spec/contracting.md`。 +> - hooks/构建检查等落地细则(给人):见 `docs/contracting-guide.md`。 + +--- + +## 0. 前置步骤:模块拆分与依赖接口盘点(Module & Dependency Scoping) + +在进入“某个模块的 Spec 生成”之前,必须先做一次轻量的边界澄清,否则后续文档会不可控、也无法高效并行。 + +### 0.1 产出物 + +建议在本模块的 Spec 目录下新增一个轻量清单(可写在 `requirements.md` 中的 Scope/Dependencies 小节,或单独写在 `scope.md`): + +- **模块边界说明(Module Scope)**:本次 Spec 只覆盖哪个模块/子域?不覆盖什么? +- **依赖清单(Dependencies)**:本模块会调用哪些外部模块/第三方? +- **依赖接口清单(Dependency Contracts)**:对每个依赖,列出“需要的接口能力”(先不管实现在哪)。 + +### 0.2 判定标准(进入第 1 阶段的准入条件) + +- 能明确回答: + - “这是哪个模块的需求?” + - “本模块对外提供什么能力?” + - “本模块依赖谁?依赖哪些接口?” +- 若依赖不明确:先补齐依赖接口清单(哪怕先是草案),再进入需求与接口建模。 + +--- + +## 1. 核心流程:三阶段协同(每次只做一个模块/一个领域边界) + +### 第一阶段:需求定义与接口建模(Requirements & API Design) + +- **目标**:明确“做什么”以及“如何交互”,建立单一可信源(SSOT)。 +- **模块化原则**:对于庞大系统,一个 Spec 上下文应**仅聚焦于一个功能模块或领域边界**(如 `ruoyi-forum`),避免跨模块需求混杂导致 AI 上下文过载。 +- **依赖先行(Dependency-First)**: + - 如果本模块依赖其他模块(或第三方服务),必须在此阶段先完成**外部依赖接口契约**(哪怕只有最小集合)。 + - **调用方**:基于依赖契约生成 Mock 并直接进入开发逻辑,无需等待被调用方实现。 + - **实现方**:后续根据该依赖契约补全真实实现,并通过契约一致性验证。 +- **产出**(默认建议放在 `spec//` 目录下,例如 `spec/ruoyi-forum/requirements.md`): + - `requirements.md`(本模块需求) + - `openapi.provider.yaml`(本模块对外提供的 API) + - `openapi.deps.yaml`(本模块依赖的外部 API 契约,供 Mock/SDK 生成) + +### 第二阶段:技术设计与方案评审(Technical Design) + +- **目标**:细化“怎么实现”,解决技术实现路径与数据模型问题。 +- **产出**:`design.md`(技术蓝图)。 +- **规范**: + - 设计方案必须引用需求 ID。 + - 若涉及跨模块调用,应明确定义:依赖 API 的 Mock 策略、超时/重试/熔断/降级、错误映射。 + +### 第三阶段:任务分解与并行开发(Task Decomposition & Implementation) + +- **目标**:将设计转化为可执行的原子任务。 +- **并行编程协作(多窗口并行)**: + - **前端/调用方 AI**:基于 `openapi.provider.yaml` + `openapi.deps.yaml` 生成 SDK + Mock,优先完成页面与业务流程。 + - **后端/提供方 AI**:实现 `openapi.provider.yaml` 对应接口,并持续导出 OpenAPI 工件供校验。 + - **依赖实现方 AI**:对照 `openapi.deps.yaml`(或其子集)补齐真实实现。 +- **产出**:`tasks.md`(执行清单)。 + +--- + +## 2. 关键文档规范 + +### 2.1 requirements.md(需求规范) + +requirements 的目标是把“自然语言需求”固化为**可追踪、可校验、可版本化**的规范。 + +必须包含以下结构: +- **Frontmatter**:包含 `feature_id`, `title`, `status`, `version`(可按团队需要扩展)。 +- **用户故事**:使用统一句式“作为…我希望…以便…”。 +- **验收标准(EARS 语法)**:每条验收标准必须带稳定 ID(推荐 `AC--NN`)。 +- **追踪映射(Traceability)**:验收标准到接口端点(以及可选 operationId)的映射。 + +#### 2.1.1 requirements.md 标准模板(可直接复制) + +```markdown +--- +feature_id: "REG" # 功能短 ID(用于拼接 US/AC/NFR 编号) +title: "用户注册" +status: "draft" # draft | review | approved | implemented +version: "0.1.0" +owners: + - "product" + - "backend" + - "frontend" +last_updated: "2026-02-23" +source: + type: "conversation" # conversation | issue | doc | meeting + ref: "" +--- + +# 用户注册(REG) + +## 1. 背景与目标 +- 背景: +- 目标: +- 非目标(Out of Scope): + +## 2. 模块边界(Scope) +- 覆盖: +- 不覆盖: + +## 3. 依赖盘点(Dependencies) +- 依赖模块/第三方: + - 依赖 A:用途说明 + - 依赖 B:用途说明 + +## 4. 用户故事(User Stories) +- [US-REG-01] 作为访客,我希望使用邮箱注册账号,以便获得会员权益。 + +## 5. 验收标准(Acceptance Criteria, EARS) +- [AC-REG-01] WHEN 访客提交有效的邮箱与密码 THEN 系统 SHALL 创建用户并返回 201。 +- [AC-REG-02] WHEN 访客提交的邮箱不合法 THEN 系统 SHALL 返回 400,并给出字段级错误信息。 +- [AC-REG-03] WHEN 邮箱已被注册 THEN 系统 SHALL 返回 400,并提示“邮箱已注册”。 + +## 6. 追踪映射(Traceability) + +| AC ID | Endpoint | 方法 | operationId(可选) | 备注 | +|------|----------|------|---------------------|------| +| AC-REG-01 | /users/register | POST | registerUser | 创建用户 | +| AC-REG-02 | /users/register | POST | registerUser | 参数校验 | +| AC-REG-03 | /users/register | POST | registerUser | 唯一性约束 | +``` + +### 2.2 OpenAPI 契约(openapi.provider.yaml / openapi.deps.yaml) + +为支持“大系统多模块 + 多窗口并行”,推荐将 OpenAPI **拆成两份**: + +- `openapi.provider.yaml`:**本模块对外提供**的 API(本模块是 Provider)。 +- `openapi.deps.yaml`:**本模块依赖**的外部 API 契约(本模块是 Consumer,便于 Mock/SDK 生成)。 + +约定: +- 两份文件都必须可被 Mock/SDK/测试工具消费。 +- Provider 文件建议带 `x-requirements: [AC-xxx]` 追踪需求。 +- Deps 文件应定义你“需要对方提供什么能力”,不以对方当前实现为准(可以先草案,后续对齐)。 + +--- + +#### 2.2.1 契约成熟度(Contract Refinement Levels) + +为减少“边实现边改契约”导致的需求偏移,本方法论引入契约成熟度等级,并要求在 OpenAPI 的 `info` 下进行**全局标记**: + +- `info.x-contract-level: L0 | L1 | L2 | L3` + +> 约定: +> - `openapi.deps.yaml` 与 `openapi.provider.yaml` 都必须标记该字段。 +> - L0/L1 用于并行启动;Provider 在进入可合并的实现阶段前应提升到 L2。 + +**L0(占位/可 Mock)** +- 目标:调用方可快速生成 Mock,跑通主流程(happy path)。 +- 必须:最小 path/method/2xx 响应骨架;统一错误响应骨架(可粗)。 +- 允许:schema 粗粒度、校验缺失、错误码不细。 + +**L1(可调用/可生成 SDK)** +- 目标:调用方可基于契约生成 SDK,并开发主要业务流程。 +- 必须:关键请求/响应字段(必填/可选)、基本状态码集合、operationId、描述。 + +**L2(可验收/可契约测试)** +- 目标:提供者可运行 Provider-driven 契约校验(schema/错误语义/边界)。 +- 必须:关键校验规则(format/minLength/enum/range 等)、结构化错误模型、核心异常分支。 + +**L3(可演进/可兼容治理)** +- 目标:长期维护与兼容演进,减少 breaking change。 +- 必须:deprecated/兼容策略、示例(examples)、变更记录(changelog 或版本策略)。 + +#### 2.2.2 从轻量切分到可验收契约:细化路径 + +- 在第 0 节(Scoping)阶段:输出依赖能力清单(自然语言即可),并将其转写为 `openapi.deps.yaml` 的 **L0** 草案。 +- 在第 1 阶段(需求与接口建模)阶段: + - 新增/完善 AC(EARS)会驱动契约从 L0 → L1(补齐核心字段、状态码与映射)。 + - 每个新增字段/错误语义/校验规则,应能追溯到某条 `AC-*`(否则属于“潜在需求漂移”)。 +- 在进入“提供者实现可合并”之前:必须将 `openapi.provider.yaml` 提升到 **L2**(达到可契约测试的程度)。 +- 在产品稳定期:逐步推动关键接口达到 **L3**(兼容治理与示例完善)。 + +> 门禁落点(实现细节):成熟度等级与合并策略、校验规则应由 `agents.md` 中的执行规则 + Git hooks/构建检查共同约束。 + +### 2.3 design.md(技术设计) + +- **内容**:系统架构、数据模型(ER 图)、核心流程、异常与重试、鉴权与审计。 +- **跨模块调用要求**:必须描述依赖 API 的失败策略(超时/重试/熔断/降级)以及错误映射。 + +### 2.4 tasks.md(任务清单) + +- **结构**:带状态的 Markdown 任务列表。 +- **要求**:任务按“本模块提供能力”与“本模块消费依赖”拆分;每个任务需标注关联 `AC-ID`。 + +--- + +## 3. 自动化协同增强(Hooks/门禁) + +本方法论要求在流程中引入自动化门禁,以确保文档与交付一致。 + +- 当 `requirements.md` / OpenAPI 文件变更时,应触发:OpenAPI Lint、Diff(兼容性检查)、契约一致性测试。 +- 当 `tasks.md` 任务完成时,应同步更新进度与相关说明。 + +> 说明:关于“AI 编码行为准则、阶段闸门、引用规则、变更策略”等执行层规范,见项目根目录 `agents.md`;关于 hooks/构建检查等落地细则,见 `docs/contracting-guide.md`。 + +--- + +## 4. 如何执行 + +1. **前置步骤**:完成模块拆分与依赖接口盘点(第 0 节)。 +2. **发起需求**:仅针对“一个模块”,生成初始 `requirements.md`。 +3. **定义契约**:输出 `openapi.provider.yaml` 与 `openapi.deps.yaml`,并进行接口走查。 +4. **架构设计**:生成 `design.md`,明确模块内边界、数据流与依赖策略。 +5. **任务执行**:生成并执行 `tasks.md`;调用方优先基于 deps 契约 Mock 并行推进。 diff --git a/scripts/check-openapi-diff.sh b/scripts/check-openapi-diff.sh new file mode 100644 index 0000000..3a01c99 --- /dev/null +++ b/scripts/check-openapi-diff.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env sh +set -eu + +# OpenAPI Breaking Change Detector (Minimal Script). +# 1. Compare the PR's openapi.provider.yaml with the version from the 'main' branch. +# 2. Detect basic breaking changes (deleted endpoints, changed methods, etc.) +# 3. Fail if breaking changes are found without explicit developer acknowledgement. + +# Base branch to compare against +BASE_BRANCH="main" + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +# Create a temporary directory for main branch files +tmp_base=$(mktemp -d) +trap 'rm -rf "$tmp_base"' EXIT + +check_breaking_changes() { + module_path="$1" + provider_file="spec/$module_path/openapi.provider.yaml" + base_file="$tmp_base/$module_path/openapi.provider.yaml" + + [ -f "$provider_file" ] || return 0 + + # Try to extract the file from the base branch + mkdir -p "$(dirname "$base_file")" + if ! git show "$BASE_BRANCH:$provider_file" > "$base_file" 2>/dev/null; then + echo "New module or provider file detected: $provider_file. Skipping diff check." + return 0 + fi + + echo "Checking breaking changes for $provider_file against $BASE_BRANCH..." + + # 1. Simple Endpoint/Method deletion check using grep/diff + # Extract paths and methods (simple grep for ' /path:' and ' get|post|put|delete:') + extract_endpoints() { + grep -E "^[[:space:]]{2}/|^[[:space:]]{4}(get|post|put|delete|patch):" "$1" | sed 's/[[:space:]]*//g' + } + + old_endpoints=$(mktemp) + new_endpoints=$(mktemp) + extract_endpoints "$base_file" > "$old_endpoints" + extract_endpoints "$provider_file" > "$new_endpoints" + + deleted_count=$(comm -23 "$old_endpoints" "$new_endpoints" | wc -l) + + if [ "$deleted_count" -gt 0 ]; then + echo "CRITICAL: Detected deleted endpoints or methods in $provider_file:" + comm -23 "$old_endpoints" "$new_endpoints" + rm -f "$old_endpoints" "$new_endpoints" + return 1 + fi + + rm -f "$old_endpoints" "$new_endpoints" + echo "OK: No obvious breaking changes in $provider_file endpoints." + return 0 +} + +# Find modules +errors=0 +for spec_dir in spec/*; do + if [ -d "$spec_dir" ]; then + module_name=$(basename "$spec_dir") + if ! check_breaking_changes "$module_name"; then + errors=$((errors + 1)) + fi + fi +done + +if [ "$errors" -gt 0 ]; then + die "Breaking change check failed. Please revert changes or mark them as compatible." +fi + +echo "All OpenAPI Breaking Change checks passed." diff --git a/scripts/check-openapi-level.sh b/scripts/check-openapi-level.sh new file mode 100644 index 0000000..56ccbde --- /dev/null +++ b/scripts/check-openapi-level.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env sh +set -eu + +# Check OpenAPI contract levels for multi-module layout. +# - For PRs targeting main: require provider contract level >= L2. +# - Always require info.x-contract-level exists and is L0-L3. +# +# Expected locations: +# - spec//openapi.provider.yaml +# - spec//openapi.deps.yaml (optional) + +require_provider_l2="${REQUIRE_PROVIDER_L2:-0}" + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +level_rank() { + case "$1" in + L0) echo 0;; + L1) echo 1;; + L2) echo 2;; + L3) echo 3;; + *) echo -1;; + esac +} + +extract_level() { + # Extracts the first occurrence of info.x-contract-level: L? + # Accepts patterns like: + # x-contract-level: L2 + # x-contract-level: "L2" + # under info: + file="$1" + + awk ' + BEGIN{in_info=0; level=""} + { + # detect "info:" at any indentation + if ($0 ~ /^[[:space:]]*info:[[:space:]]*$/) { in_info=1; info_indent=match($0,/[^ ]/)-1; next } + if (in_info==1) { + # if indentation decreases or new top-level key begins, leave info block + cur_indent=match($0,/[^ ]/)-1; + if (cur_indent <= info_indent && $0 ~ /^[^[:space:]]/ ) { in_info=0 } + } + if (in_info==1 && level=="" && $0 ~ /^[[:space:]]*x-contract-level:[[:space:]]*/) { + line=$0 + sub(/^[[:space:]]*x-contract-level:[[:space:]]*/,"",line) + gsub(/"|\047/,"",line) + # strip comments + sub(/[[:space:]]*#.*/,"",line) + gsub(/[[:space:]]+/,"",line) + level=line + } + } + END{print level} + ' "$file" +} + +check_file_level() { + file="$1" + kind="$2" # provider|deps + + [ -f "$file" ] || die "Missing OpenAPI file: $file" + + level="$(extract_level "$file" | tr -d '\r')" + [ -n "$level" ] || die "$file: missing info.x-contract-level (expected under info: x-contract-level: L0|L1|L2|L3)" + + rank="$(level_rank "$level")" + [ "$rank" -ge 0 ] || die "$file: invalid x-contract-level '$level' (expected L0|L1|L2|L3)" + + if [ "$kind" = "provider" ] && [ "$require_provider_l2" = "1" ]; then + if [ "$rank" -lt 2 ]; then + die "$file: provider contract-level must be >= L2 for merge-to-main (current: $level)" + fi + fi + + echo "OK: $file level=$level" +} + +# Find all provider openapi files under spec/* +provider_files="$(find spec -mindepth 2 -maxdepth 2 -type f -name 'openapi.provider.yaml' 2>/dev/null || true)" +[ -n "$provider_files" ] || die "No provider OpenAPI found. Expected at least one spec//openapi.provider.yaml" + +# Provider files always must have a valid level; for main merges, require >= L2 +for f in $provider_files; do + check_file_level "$f" provider + +done + +# Deps files are optional, but if present must have valid level +for d in $(find spec -mindepth 2 -maxdepth 2 -type f -name 'openapi.deps.yaml' 2>/dev/null || true); do + check_file_level "$d" deps + +done + +echo "All OpenAPI contract level checks passed." diff --git a/scripts/check-traceability.sh b/scripts/check-traceability.sh new file mode 100644 index 0000000..ee24eb5 --- /dev/null +++ b/scripts/check-traceability.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env sh +set -eu + +# Check AC (Acceptance Criteria) Traceability. +# 1. Collect all AC IDs defined in spec/**/requirements.md (format: [AC-FEATURE-NN]) +# 2. Collect all AC IDs referenced in src/** and test/** (format: [AC-FEATURE-NN]) +# 3. Verify every referenced AC ID exists in requirements. + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +# 1. Collect defined ACs from requirements +defined_acs_file=$(mktemp) +# Matches patterns like [AC-REG-01] +find spec -name "requirements.md" -exec grep -o "\[AC-[A-Z0-9]\+-[0-9]\{2\}\]" {} + | sed 's/.*\[\(AC-[A-Z0-9]\+-[0-9]\{2\}\)\].*/\1/' | sort -u > "$defined_acs_file" + +if [ ! -s "$defined_acs_file" ]; then + echo "Warning: No AC IDs found in spec/**/requirements.md. Skipping traceability check." + rm -f "$defined_acs_file" + exit 0 +fi + +echo "Found $(wc -l < "$defined_acs_file") defined AC IDs in requirements." + +# 2. Collect referenced ACs from src/ and test/ +referenced_acs_file=$(mktemp) +# Search in src and test directories +find src test -type f \( -name "*.java" -o -name "*.vue" -o -name "*.ts" -o -name "*.tsx" -o -name "*.md" \) -exec grep -o "\[AC-[A-Z0-9]\+-[0-9]\{2\}\]" {} + | sed 's/.*\[\(AC-[A-Z0-9]\+-[0-9]\{2\}\)\].*/\1/' | sort -u > "$referenced_acs_file" + +if [ ! -s "$referenced_acs_file" ]; then + echo "OK: No AC references found in code. Traceability check skipped." + rm -f "$defined_acs_file" "$referenced_acs_file" + exit 0 +fi + +echo "Found $(wc -l < "$referenced_acs_file") AC references in code." + +# 3. Verify references +missing_acs=0 +while read -r ac; do + if ! grep -q "^$ac$" "$defined_acs_file"; then + echo "ERROR: Referenced AC ID '$ac' not found in any requirements.md" + missing_acs=$((missing_acs + 1)) + fi +done < "$referenced_acs_file" + +rm -f "$defined_acs_file" "$referenced_acs_file" + +if [ "$missing_acs" -gt 0 ]; then + die "Traceability check failed: $missing_acs unknown AC reference(s) found." +fi + +echo "OK: All AC references in code are traceable to requirements." diff --git a/spec/contracting.md b/spec/contracting.md new file mode 100644 index 0000000..185403e --- /dev/null +++ b/spec/contracting.md @@ -0,0 +1,65 @@ +# contracting.md(ai 必须遵守:契约成熟度与门禁规则) + +本文件仅包含**编码智能体必须遵守**的契约约束与门禁规则(面向执行与校验)。 + +> 约定:若本文件与 `agents.md` 冲突,以 `agents.md` 为准;若本文件与 `spec//requirements.md` 或 OpenAPI 契约冲突,必须先修正文档再编码,禁止猜测。 + +--- + +## 1. 文件与字段硬约束 + +- 本模块的 OpenAPI 契约拆分为: + - `openapi.provider.yaml`:本模块对外提供的 API(provider) + - `openapi.deps.yaml`:本模块依赖的外部 API 契约(consumer 需求侧) + +- 两份 OpenAPI 都必须在 `info` 下声明成熟度(全局标记,不按 operation 级): + - `info.x-contract-level: L0 | L1 | L2 | L3` + +- provider 端接口(建议强制): + - `operationId` 必须存在且在同文件内唯一 + - 应通过 `x-requirements: [AC-...]` 或 requirements 的 traceability 表建立 AC 追踪 + +--- + +## 2. 契约成熟度(L0-L3)最低要求 + +- **L0(占位/可 mock)**:允许 schema 粗粒度;仅用于并行启动与 mock。 +- **L1(可调用/可生成 sdk)**:关键请求/响应字段 required/optional 明确;基本状态码集合明确。 +- **L2(可验收/可契约测试)**:关键字段校验规则与错误语义稳定;可进行 provider-driven schema 校验。 +- **L3(可演进/可兼容治理)**:具备兼容策略与示例;可治理 breaking change。 + +--- + +## 3. 细化路径(粗 → 细)执行规则 + +- 新增/调整 **AC** 会驱动契约细化;任何字段/错误语义/校验规则的新增,必须能追溯到某条 `AC-*`。 +- 当实现准备进入可合并状态(尤其是自动合并)时,必须先把 `openapi.provider.yaml` 提升到 **L2**。 + +--- + +## 4. 门禁规则(软/硬) + +### 4.1 feature 分支(软门禁) + +允许: +- 使用 `openapi.deps.yaml (L0/L1+)` 生成 mock/sdks,推进调用方开发。 + +禁止: +- 未满足 `openapi.provider.yaml >= L2` 的情况下,将 provider 实现以“自动合并”方式进入 main。 + +### 4.2 main 分支(硬门禁) + +满足以下条件前,禁止合并(或禁止自动合并): +- `openapi.provider.yaml >= L2` +- 契约测试通过(响应符合 openapi schema) +- openapi diff 检查通过(无未声明 breaking change) +- 需求追踪检查通过(AC 引用未断裂) + +--- + +## 5. 最小检查清单(编码智能体自检) + +- [ ] openapi 文件可解析 +- [ ] `info.x-contract-level`存在且为 L0-L3 +- [ ] provider: operationId 唯一 +- [ ] provider: 能追踪到 AC(x-requirements 或 traceability)