Skip to content

Commit 3a795fa

Browse files
committed
release: ai-delivery-spec 5.4.1
Human-First quality gates: intake artifact routing, UIACT/ACT split, demo-scaffolding and nested-iframe blocking, CSS effect and type-scale scans, cross-surface binding terms, state-machine purity, decision/unknown conflict detection, aesthetics precondition, legacy asset disposition contract, unified PRD template restructured into human body + machine annex, quality_gate.py split into three mixins within line budget. Single-artifact PASS no longer counts as delivery closure; full/handoff combined gate is required before claiming completion. Validated on Windows with Python 3.12: pytest 7/7, 23 script tests, maintainer assurance suite, release claims, and 18 real-project integration cases all pass.
1 parent 22675ed commit 3a795fa

24 files changed

Lines changed: 2545 additions & 1667 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## 5.4.1 - 2026-07-27
4+
5+
- 修复首跑断裂:`gate --profile requirement``artifact: requirement_intake` 识别单需求准入卡并改用独立 intake schema 校验,不再把扁平 intake.yaml 误判为需求登记册;修复提示指向正确模板。
6+
- UI 动作与业务动作分层:纯界面动作使用 `UIACT-*`,豁免业务验收锚点与 PRD 动作回链;`ACT-*` 业务动作合同不变,handoff 不再把导航/关闭/页签切换当作业务缺口。
7+
- 原型门禁拦截演示脚手架与套壳:可见 UI 出现验收场景/体验身份/E2E 控制台/继承预览等演示元素报 `PROTO-DEMO-SCAFFOLDING-VISIBLE`;iframe 嵌入本地产品页报 `PROTO-NESTED-PRODUCT-IFRAME`
8+
- CSS 扫描升级:HTML 交互元素类无样式定义报 `unstyled-control-class`,主文本字号低于 11px 报 `unreadable-type-scale`,并识别按钮无主次层级与同页双导航。
9+
- PRD 语义纯净检查:状态机状态列混入 API/字段/动作 ID 报 `PRD-STATE-SEMANTIC-POLLUTION`("状态机 / API"等工程映射列不误判);同一主题同时登记为已确认决策和开放未知项报 `PRD-CONFIRMED-OPEN-UNKNOWN-CONFLICT`;稳定 ID 内含 TODO 子串不再误判为未跟踪未知项。
10+
- 跨端绑定词一致性:PRD frontmatter `binding_terms`(法定名词、领域术语)必须同时出现在 PRD 正文与原型可见文本,缺失报 `HANDOFF-BINDING-TERM-MISSING`
11+
- 美学方向前置:handoff/full 组合门禁要求高保真原型具备 `DEC-AESTHETIC-*` 美学决策记录(视觉方向、参考产品、禁止风格、字号层级、按钮体系、密度),未确认报 `HANDOFF-AESTHETIC-UNDECIDED`
12+
- 存量资产处置契约:Stage 0 盘点条目支持 `disposition`(adopt_page/inherit_layout/rebuild_interaction/reuse_component/discard),整页采用、局部继承、重构、废弃成为正式处置方式。
13+
- Human-First 模板重构:统一 PRD 模板显式分层——正文面向业务/产品/开发/测试阅读(角色阅读入口、一事一处、密度控制),工程附录面向机器(稳定 ID、字段、API、机器验收);ADS 锚点保持兼容。
14+
- 出口条件明确:单产物 PASS 不等于交付闭环,宣称最终完成前必须 `gate --profile full`(或 handoff)组合门禁通过;`not_proven` 边界声明不变。
15+
- quality_gate.py 按职责拆分为 PRD/原型/交接三个 mixin 模块,单文件回到行数预算内,`from quality_gate import Gate` 兼容不变。
16+
- 新 finding code 全部配备具体修复原因与示例,`explain-finding` 不再要求读源码猜合同。
17+
- H2 章节结构分析包含完整 H3/H4 子树,角色旅程等嵌套内容不再被截断。
18+
- `examples/minimal-v5/intake.yaml` 补齐 intake 必填字段,入门示例自身通过 `gate --profile requirement`
19+
320
## 5.4.0 - 2026-07-25
421

522
- 新增九个可进入、可停止、可续接的需求工作站;`frame/explore` 明确为准入前工作区,正式 `REQ-*` 生命周期仍从 intake 开始。

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# AI Delivery Spec 5.4.0 — Enter at Any Requirement Stage, Leave with a Usable Artifact
1+
# AI Delivery Spec 5.4.1 — Enter at Any Requirement Stage, Leave with a Usable Artifact
22

33
> 需求一来就写 PRD,低价值需求也进入重型设计?
44
>
@@ -14,13 +14,13 @@
1414
正文让客户、产品和传统开发顺序读懂,同文档工程附录让测试与 AI Coding 精确执行。
1515
只有大项目、持续变更、多投影或强审计场景才启用分片 Product Truth。
1616

17-
[![Version](https://img.shields.io/badge/version-5.4.0-0052A4.svg)]()
17+
[![Version](https://img.shields.io/badge/version-5.4.1-0052A4.svg)]()
1818
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
1919
[![Stars](https://img.shields.io/github/stars/franklinxkk/ai-delivery-spec?style=social)](https://github.com/franklinxkk/ai-delivery-spec)
2020

21-
<!-- CLAIM: CLM-ADOPTION-20260726; as_of=2026-07-26; evidence=author-checked-public-platform-links-below -->
22-
**公开采用信号 / Public Adoption Signals(截至 2026-07-26**
23-
[ClawHub 1100+ 次下载](https://clawhub.ai/franklinxkk/skills/ai-delivery-spec) · [skills.sh 安装页](https://www.skills.sh/franklinxkk/ai-delivery-spec) ·
21+
<!-- CLAIM: CLM-ADOPTION-20260727; as_of=2026-07-27; evidence=author-checked-public-platform-links-below -->
22+
**公开采用信号 / Public Adoption Signals(截至 2026-07-27**
23+
[ClawHub 1200+ 次下载](https://clawhub.ai/franklinxkk/skills/ai-delivery-spec) · [skills.sh 安装页](https://www.skills.sh/franklinxkk/ai-delivery-spec) ·
2424
[SkillHub TRACE 4.7/5](https://skillhub.cn/skills/ai-delivery-spec),平台安全扫描未发现 P0/P1 风险。
2525
动态数字是作者核对的公开快照,不替代项目适用性判断;领域证据边界见 [release-status.yaml](maintainer/evals/evidence/release-status.yaml)
2626

@@ -134,9 +134,9 @@ openclaw skills install @franklinxkk/ai-delivery-spec
134134
研发排期、Sprint/任务、代码、CI/CD、部署执行、监控和运营属于下游系统。
135135
本项目只记录它们与需求/验收有关的外部引用,不接管流程。
136136

137-
## 5.4.0 — Requirement Lifecycle Workstations|需求全生命周期工作站
137+
## 5.4.1 — Requirement Lifecycle Workstations + Human-First Gates|需求全生命周期工作站与可读性门禁
138138

139-
5.4.0 把需求工作改为九个可进入、可停止、可续接的工作站,同时保持一个正式生命周期和一套门禁状态。阶段产物用 `artifact/stage` 和语言无关 `ADS:*` 锚点声明语义,跨会话才携带 `resume_context`;不要求每次运行脚本路由。
139+
5.4 把需求工作改为九个可进入、可停止、可续接的工作站,同时保持一个正式生命周期和一套门禁状态。5.4.1 把门禁从"结构齐全"扩展到"可读、可用、可闭环":UI 动作与业务动作分层(`UIACT-*`/`ACT-*`)、演示脚手架与 iframe 套壳拦截、CSS 类生效与字号下限、绑定词跨端一致、状态机语义纯净、intake 工件按自身 schema 校验。单产物 PASS 不等于交付闭环:宣称最终完成前必须通过 `gate --profile full` 组合门禁。阶段产物用 `artifact/stage` 和语言无关 `ADS:*` 锚点声明语义,跨会话才携带 `resume_context`;不要求每次运行脚本路由。
140140

141141
你会感受到五个变化:
142142

@@ -263,7 +263,7 @@ python scripts/ai_delivery_spec_cli.py impact --truth requirements/truth/compile
263263
| 位置 | 工具类型 | 责任 |
264264
|---|---|---|
265265
| 上游 | 产品发现、调研、工作坊 | 发现机会、证据和策略假设 |
266-
| **需求管理内核** | **AI Delivery Spec 5.4.0** | 问题/方案 → 准入 → 澄清 → 基线 → 变更 → 验收 |
266+
| **需求管理内核** | **AI Delivery Spec 5.4.1** | 问题/方案 → 准入 → 澄清 → 基线 → 变更 → 验收 |
267267
| 下游 | Spec Kit、项目/研发管理工具 | 技术方案、任务、排期和依赖执行 |
268268
| 下游 | Codex、Trae、Cursor、Qoder 等 | 依据已基线需求编码、测试和修改 |
269269
| 外部证据 | CI、测试、发布、监控平台 | 向需求验收回传可引用证据 |

SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: ai-delivery-spec
33
description: 将一句话想法、客户材料、存量系统或 ToC/ToB/ToG 需求,按使用者指定的进入阶段和停止阶段,转化为可评审、可实施、可追溯、可验收的人类与 Coding Agent 共用需求产物。支持问题定义、方案探索、需求准入、澄清、统一 PRD、工程原型、评审基线、变更影响和验收证据;不负责排期、编码、CI/CD、部署和运营。
44
---
55

6-
# AI Delivery Spec 5.4.0 — Requirement Lifecycle Workstations|需求全生命周期工作站
6+
# AI Delivery Spec 5.4.1 — Requirement Lifecycle Workstations|需求全生命周期工作站
77

88
本 Skill 是 Requirement Management Kernel:让业务、产品、设计、前后端、架构、需求交付/技术负责人、测试、合规和 Coding Agent 在需求任一阶段进入,得到当前需要的最小合格产物后离开;也可在用户明确要求时持续完成端到端闭环。
99

@@ -74,7 +74,7 @@ python scripts/ai_delivery_spec_cli.py gate --profile explore --artifact solutio
7474
python scripts/ai_delivery_spec_cli.py gate --profile clarify --artifact requirement-brief.md
7575
```
7676

77-
正式规格沿用 `gate --profile requirement|prd|prototype|handoff|full`。静态门禁必须输出 `not_proven`,不能把结构通过宣传为领域正确、真实运行或客户签收。
77+
正式规格沿用 `gate --profile requirement|prd|prototype|handoff|full`。静态门禁必须输出 `not_proven`,不能把结构通过宣传为领域正确、真实运行或客户签收。单产物 PASS 不等于交付闭环;宣称最终完成前必须 `gate --profile full`(或 handoff)组合门禁通过。
7878

7979
5.4 模板用语言无关的 `<!-- ADS:* -->` 锚点,标题可按团队语言/模板改变。`resume_context` 记录相对路径、阶段和 SHA-256;漂移、缺失和路径越界必须阻断。大项目仍用执行检查点和 ID Slice,产物断点不能替代执行状态。
8080

agents/openai.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
interface:
22
display_name: "AI Delivery Spec"
3-
version: "5.4.0"
3+
version: "5.4.1"
44
short_description: "需求任一阶段进入,按目标交付可读、可实现、可追溯的最小合格产物。"
55
default_prompt: >
66
使用 $ai-delivery-spec 作为需求管理内核。先从用户语义和已有产物识别 entry_stage 与 target_stage;

examples/minimal-v5/intake.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1+
schema_version: 5.4.0
2+
artifact: requirement_intake
3+
stage: intake
4+
requirement_id: REQ-MINIMAL-001
15
title: 制度列表增加仅看当前有效筛选
26
outcome: 员工可一键排除已失效制度,减少误用旧制度
37
owner: 产品负责人
48
document_language: zh-CN
59
source_refs: [SRC-MINIMAL-001]
610
value: medium
11+
priority: P2
12+
complexity:
13+
band: S
14+
dimensions: []
15+
target_iteration: null
16+
dependencies: []
717
value_evidence:
818
- 已确认的内部用户反馈:列表中混有已失效制度
919
roles: [ROLE-EMPLOYEE]

examples/spec.config.example.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ assurance:
2929
human_gate_on: [regulated, money, safety, privacy, tenant_isolation, ai_writeback, migration]
3030
execution:
3131
environment: development
32-
expected_skill_version: 5.4.0
32+
expected_skill_version: 5.4.1
3333
dependency_policy: strict
3434
high_risk_failure: block
3535
low_risk_failure: human_review

maintainer/evals/evidence/release-status.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
schema_version: 5.3.0
2-
skill_version: 5.4.0
2+
skill_version: 5.4.1
33
runtime: pure_v5
44
domain_packs:
55
count: 7

maintainer/tests/test_execution_state.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import subprocess
77
import sys
8+
import re
89
import tempfile
910
import json
1011
import yaml
@@ -191,10 +192,11 @@ def main() -> int:
191192
raise AssertionError("approved low-risk validator outage was not recorded explicitly")
192193

193194
old_skill = temp / "SKILL.md"
194-
old_skill.write_text(
195-
(ROOT / "SKILL.md").read_text(encoding="utf-8").replace("AI Delivery Spec 5.4.0", "AI Delivery Spec 4.9.15", 1),
196-
encoding="utf-8",
197-
)
195+
skill_text = (ROOT / "SKILL.md").read_text(encoding="utf-8")
196+
drifted, count = re.subn(r"AI Delivery Spec \d+\.\d+\.\d+", "AI Delivery Spec 4.9.15", skill_text, count=1)
197+
if count != 1:
198+
raise AssertionError("SKILL.md title does not carry a semantic version")
199+
old_skill.write_text(drifted, encoding="utf-8")
198200
blocked = temp / "blocked.yaml"
199201
result = run(
200202
"create", "--truth", str(TRUTH), "--config", str(CONFIG),

maintainer/tests/test_v540_readme_commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def run(*args: str) -> subprocess.CompletedProcess[str]:
4141
failures.append(f"README misses progressive-disclosure marker: {marker}")
4242

4343
version = run("version")
44-
if version.returncode != 0 or version.stdout.strip() != "5.4.0":
44+
if version.returncode != 0 or version.stdout.strip() != "5.4.1":
4545
failures.append("README public version path failed: " + version.stdout + version.stderr)
4646

4747
with tempfile.TemporaryDirectory(prefix="ads-readme-") as temp_name:
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Regression tests for the v5.4.1 Human-First and brownfield gate fixes."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
from pathlib import Path
7+
8+
ROOT = Path(__file__).resolve().parents[2]
9+
sys.path.insert(0, str(ROOT / "scripts"))
10+
sys.path.insert(0, str(ROOT / "scripts" / "validators"))
11+
12+
from quality_gate import Gate # noqa: E402
13+
from scan_prototype_css import scan # noqa: E402
14+
from prd_structure import _section_bodies # noqa: E402
15+
16+
17+
def codes(gate: Gate) -> set[str]:
18+
return {item.code for item in gate.findings}
19+
20+
21+
class MemoryGate(Gate):
22+
def __init__(self, documents: dict[str, str]):
23+
super().__init__()
24+
self.documents = {str(Path(name)): value for name, value in documents.items()}
25+
26+
def read(self, path: Path) -> str:
27+
return self.documents[str(path)]
28+
29+
30+
def test_h2_uses_full_h3_subtree() -> None:
31+
raw = """## 4. 端到端角色旅程
32+
33+
### 4.1 企业旅程 FLOW-DEMO-001
34+
ROLE-USER 进入页面,提交后得到可见成功结果;失败时保留输入并恢复。
35+
36+
## 5. 业务流程与状态
37+
正文。
38+
"""
39+
sections = dict(_section_bodies(raw))
40+
assert "ROLE-USER" in sections["4. 端到端角色旅程"]
41+
assert "FLOW-DEMO-001" in sections["4. 端到端角色旅程"]
42+
43+
44+
def test_todo_inside_stable_id_is_not_unknown_marker() -> None:
45+
gate = Gate()
46+
raw = """---
47+
open_p0_unknown_ids: []
48+
---
49+
## 模块
50+
REG-RPT-TODO 是待办区域,不是 TODO 占位符。
51+
"""
52+
gate._check_unknowns(Path("prd.md"), raw, gate._frontmatter(raw), stage="specify", scope_refs=set())
53+
assert "PRD-UNTRACKED-UNKNOWN" not in codes(gate)
54+
55+
56+
def test_uiact_is_allowed_without_business_ac() -> None:
57+
path = Path("prototype.html")
58+
raw = """<!doctype html><html><head><style>
59+
.btn{border:1px solid #1677ff;padding:8px 12px;background:#fff}.page{display:block}.hidden{display:none!important}
60+
</style></head><body>
61+
<section class="page" data-testid="page-VIEW-DEMO-001" data-state="default">
62+
<div data-testid="region-REG-DEMO-001"><button class="btn" data-action="UIACT-TAB-NEXT">下一页签</button>
63+
<button class="btn" data-action="ACT-DEMO-SAVE" data-ac="AC-DEMO-001">保存</button></div></section>
64+
<script>document.addEventListener('click',e=>{const el=e.target.closest('[data-action]');if(!el)return;switch(el.dataset.action){case 'UIACT-TAB-NEXT':document.body.setAttribute('data-state','tab-next');break;case 'ACT-DEMO-SAVE':document.body.setAttribute('data-state','saved');break;}});</script>
65+
</body></html>"""
66+
gate = MemoryGate({str(path): raw})
67+
gate.check_prototype(path, "L3")
68+
found = codes(gate)
69+
assert "PROTO-UNSTABLE-ACTION" not in found
70+
assert "PROTO-ACTION-NO-AC" not in found
71+
assert "PROTO-UNHANDLED-ACTION" not in found
72+
73+
74+
def test_binding_terms_are_checked_across_handoff() -> None:
75+
prd = Path("PRD.md")
76+
prototype = Path("prototype.html")
77+
prd_raw = """---
78+
page_contract_view_ids: [VIEW-DEMO-001]
79+
binding_terms: [道路运输经营许可证]
80+
---
81+
REQ-DEMO-001 ACT-DEMO-001 AC-DEMO-001
82+
- id: AC-DEMO-001
83+
"""
84+
prototype_raw = '<section data-testid="page-VIEW-DEMO-001">行业经营许可</section>'
85+
gate = MemoryGate({str(prd): prd_raw, str(prototype): prototype_raw})
86+
gate.check_handoff(prd, [prototype], "L2")
87+
assert "HANDOFF-BINDING-TERM-MISSING" in codes(gate)
88+
89+
90+
def test_demo_and_local_iframe_are_blocked() -> None:
91+
path = Path("prototype.html")
92+
raw = '<section data-testid="page-VIEW-DEMO-001">验收场景<iframe src="child.html"></iframe></section>'
93+
gate = MemoryGate({str(path): raw})
94+
gate.check_prototype(path, "L2")
95+
found = codes(gate)
96+
assert "PROTO-DEMO-SCAFFOLDING-VISIBLE" in found
97+
assert "PROTO-NESTED-PRODUCT-IFRAME" in found
98+
99+
def test_css_scans_unstyled_controls_and_tiny_primary_text() -> None:
100+
findings = scan("""<style>.primary-btn{color:#fff}button{font-size:9px}</style><button class="btn primary">保存</button>""")
101+
kinds = {item["kind"] for item in findings}
102+
assert "unstyled-control-class" in kinds
103+
assert "unreadable-type-scale" in kinds
104+
105+
106+
def test_state_columns_reject_api_ids() -> None:
107+
gate = Gate()
108+
raw = """REQ-DEMO-001 AC-DEMO-001
109+
| 当前状态 | 动作 | 下一状态 |
110+
|---|---|---|
111+
| 待处理 | 复检 | API-RISK-RECHECK 复检 |
112+
"""
113+
gate._check_testability(Path("PRD.md"), raw, {}, "L2")
114+
assert "PRD-STATE-SEMANTIC-POLLUTION" in codes(gate)
115+
116+
117+
if __name__ == "__main__":
118+
tests = [value for name, value in sorted(globals().items()) if name.startswith("test_")]
119+
for test in tests:
120+
test()
121+
print(f"PASS: {len(tests)} v5.4.1 Human-First gate regressions")

0 commit comments

Comments
 (0)