前三章一直在处理同一类问题,短链接项目里的需求和实现都在往前走,工程底线却没有一个稳定的落点。这个功能要求测试,换一个功能就只跑 happy path。这次记得校验 URL,下次又把输入原样写进数据库。每一次生成单独看都说得过去,合在一个仓库里,代码质量、安全边界和兼容性却全靠当时那个会话记不记得。
Feature Spec 解决不了这个问题,它只负责一个功能的 What 和 Why,不应该在每一份 spec.md 里重复"不能泄露密钥"、"已有链接不能失效"、"修改行为要补回归测试"。这类规则不属于某一个功能,它们比功能活得更久,还要约束后面的计划、任务和实现。Spec Kit 把这个跨功能的规则集放在 .specify/memory/constitution.md,称为 Constitution。
这一章继续使用前面的短链接项目,我们先判断什么内容值得写进 Constitution,再从项目风险里提炼一组可以检查的原则,实际生成项目宪法。最后沿着后续命令看这些原则如何变成门禁,以及确实需要额外复杂度时,应该怎样记录例外。
一、Constitution 解决什么问题
Constitution 常被翻译成"项目宪法",这个名字听起来很重,但它解决的问题很具体。AI Coding Agent 每次开始工作时,不能只知道当前功能要做什么,还得知道这个项目认为什么是不可接受的实现。
短链接服务的核心行为是创建和跳转。假设我们只给 Agent 一份"增加链接过期功能"的 Feature Spec,它完全可能写出一段能通过验收场景的代码,却顺手改变已有链接的响应格式,或者只在页面上隐藏过期链接,跳转接口仍然可用。功能规格没有要求它破坏旧行为,当然也没有逐项重申整个项目的工程底线。没有共享规则时,Agent 只能从现有代码和当前对话中猜。
Constitution 就是那组不该反复猜的答案,它可以规定已签发的有效短链不得被悄悄映射到另一个网址,任何行为修改都必须有可执行测试,日志不能记录带敏感查询参数的完整目标 URL,公共契约发生破坏性变化时必须给出迁移和回滚方案。这些规则会被后续命令读取,成为生成技术方案和检查实现时的共同依据。
它和常见的几类文档容易混淆,边界可以这样判断。
| 产物 | 负责的范围 | 典型内容 | 不该放进去的内容 |
|---|---|---|---|
| Constitution | 整个项目长期有效的工程原则 | 测试底线、安全边界、兼容性政策、复杂度约束 | 单个功能的页面、字段和验收流程 |
| Feature Spec | 一个功能的 What 和 Why | 用户故事、功能需求、边界情况、成功标准 | 框架、数据库和代码结构 |
| Implementation Plan | 一个功能的 How | 技术选型、数据模型、接口契约、项目结构 | 跨项目永久有效的口号 |
| 编码规范 | 代码表现形式 | 命名、格式化、目录约定、语言习惯 | 产品行为和架构治理 |
这张表里最重要的是"长期有效"。如果一条规则只服务于当前功能,它应该进入 spec.md 或 plan.md。如果一条规则会被频繁调整,或者只是团队偏好的写法,它更适合放进 linter、formatter 或开发指南。Constitution 应当保留那些跨功能、足够稳定、违反后会产生明确工程风险的规则。
因此,Constitution 不是一份更长的 system prompt。Prompt 负责告诉 Agent 这一次做什么,Constitution 负责告诉它无论做什么都不能越过哪些边界。前者随任务变化,后者跟着项目版本化。
二、从现有项目里提炼原则
新项目可以从已知风险开始立规则,存量项目则不能只靠团队开会想象"理想工程应该是什么样"。最可靠的原则通常已经散落在仓库和事故记录里,只是还没有被收拢。
README 和架构决策记录能告诉我们项目为何选择当前边界,CI 配置能暴露真正执行的质量门禁,测试目录能说明团队重点保护哪些行为,部署脚本和运行手册记录了环境约束,故障复盘则直接指出哪些错误值得用项目级规则防止再次发生。代码本身也有价值,但只能当证据,不能把"现在一直这么写"自动升级成宪法。现有代码里既有深思熟虑的约束,也有历史偶然和待偿还的技术债。
提炼时可以给每条候选规则过四个问题。
- 它是否会约束多个功能,而不是只解决当前需求。
- 违反它是否会造成可描述的失败,例如错误跳转、数据泄露、旧链接失效或无法回滚。
- 评审者能否根据文件、测试或测量结果判断它有没有满足。
- 这条规则是否足够稳定,值得让后续所有计划承担遵守成本。
四个问题都能回答,才适合进入 Constitution。"代码要优雅"缺少可验证的标准,评审者无法判断是否遵守,因此不合格。"所有改动都必须达到 100% 覆盖率"虽然能测,却未必和风险对应,还可能诱导团队为数字写无意义测试。"修复缺陷必须附带一个在修复前失败的回归测试"既跨功能,又能检查,还直接防止同类问题复发,就是一条合格的项目原则。
规则的措辞也需要克制。Spec Kit 的 Constitution 命令会检查原则是否具有规范性。"应该尽量"没有说明什么是必须做到的,也无法作为实施和评审的判断标准。真正不可协商的要求用 MUST,允许在明确条件下偏离的要求用 SHOULD 或者直接写出例外成立的条件。重点不是把英文单词写成大写,而是让人知道什么证据算通过,什么情况会阻塞实施。
短链接项目还没有业务代码,但前三章已经暴露了足够多的失败模式。slug 碰撞会把访问者送到错误页面,过期行为曾被 AI 静默决定,删除接口缺少鉴权,日志和统计可能把原始 URL 里的敏感查询参数留下来,新一轮优化又可能在没有测量的情况下提前引入 Redis。这些风险比凭空罗列"高质量、可维护、高性能"更适合作为立法依据。
三、代码质量、测试与用户体验怎样写成硬规则
我们先处理最容易写成口号的三类原则。代码质量、测试和用户体验谁都赞同,困难在于怎样把它们写成 Agent 能执行、评审者能验证的约束。
短链接服务最重要的质量不是抽象层数或者函数长度,而是映射正确性。一个有效短码只能对应一个原始 URL,发出去之后不能被悄悄改指向,生成发生碰撞时不能覆盖已有记录。于是项目宪法的第一条没有写"保持高质量",而是直接写外部不变量。
markdown
### I. Mapping Correctness
Every active short code MUST resolve to exactly one original URL. A code MUST NOT be
silently remapped to a different URL after issuance. Creation MUST handle collisions
without overwriting an existing mapping. Expired, deleted, malformed, and unknown codes
MUST produce distinct, documented outcomes.
这条原则把第 1 章里最危险的事故抬到了项目级。以后不管增加过期、批量导入还是自定义短码,只要可能改变映射关系,计划阶段都必须回答如何守住这个不变量。它没有规定用唯一索引、事务还是应用层重试,那些属于 How,留给具体计划决定。
测试原则同样不能只写"必须有测试"。这种规则会产生一批断言返回值非空的低价值用例,却没有保证关键行为被保护。我们的第二条原则直接点出测试来源和风险面。
markdown
### II. Behavior-Driven Verification
Every behavior change MUST have executable tests derived from its acceptance scenarios.
The creation and redirect paths MUST have integration coverage. Collision handling,
invalid URLs, unknown codes, expiration, and authorization boundaries MUST have regression
coverage whenever the behavior exists or changes. A bug fix MUST include a test that fails
without the fix.
这里没有强制所有代码走 TDD,也没有随意规定一个测试覆盖率指标。项目真正关心的是外部行为有没有被验收场景覆盖,关键路径有没有跨组件验证,修过的缺陷会不会再次出现。覆盖率数字本身说明不了这些风险有没有被守住,所以它只能作为参考,不能当成验收标准。
用户体验原则从失败路径入手,成功页面很容易被关注,用户真正无助的时刻往往是短链不存在、已过期或者没有管理权限。项目宪法要求这些状态产生不同且有说明的结果,错误信息告诉用户发生了什么、下一步还能做什么,同时不暴露堆栈和存储细节。这样一来,"系统友好"不再是一句形容词,而是可以通过验收场景检查的行为。
同一条原则还约束可观测性,创建失败、跳转失败、碰撞和鉴权拒绝需要结构化诊断,但完整目标 URL 不能直接写进日志,因为查询参数里可能带重置密码令牌、会话标识或者客户数据。可观测不等于把一切都记录下来,能排障和不泄露敏感信息必须同时成立。
四、安全、性能与兼容性原则不能靠想象
安全规则最常见的错误是只写"遵循最佳实践"。最佳实践会随上下文变化,Agent 无法据此判断一个 javascript: URL 能不能被保存,删除接口是否允许匿名调用。短链接项目的安全边界需要落到具体输入和操作上。
我们的 Constitution 只允许 http 和 https 目标地址,其他协议必须通过修宪才能放开。密钥不能进入代码、错误响应和日志,查看、修改、删除链接的管理操作必须经过身份验证和授权。这里特意区分"创建"和"管理",公开创建短链是否需要登录仍然可以由产品规格决定,但一条链接已经产生之后,谁能看内部信息、谁能改变或删除它,不能交给每个功能临时发挥。
还有一个容易被误解的边界,服务把浏览器重定向到用户提交的网址,不等于服务端应该主动请求那个网址。如果以后增加网页预览或安全扫描,服务端网络请求会引入 SSRF 风险,那应当在对应 Feature Spec 和技术计划里单独建模。Constitution 只保留长期成立的底线,目标 URL 始终是不可信输入,不能因为已经存进数据库就获得访问内部网络或凭据的能力。
性能原则更不应该凭空写"响应必须低于 100 毫秒",这个数字没有负载、硬件、数据规模和测量方法,精确得毫无意义。项目宪法要求任何性能门禁都同时给出工作负载、环境、百分位和目标。只有这些条件齐全,"快"才从愿望变成可以复现的结论。
markdown
### V. Measured Performance
Performance claims MUST name a workload, environment, percentile, and target before they
become a gate. Changes to the redirect path MUST include a repeatable benchmark or load test
when the plan predicts a material latency or throughput impact. Caching, queues, replicas,
or other operational complexity MUST be introduced only after measurements show that the
simpler design misses an approved target.
这条规则没有禁止 Redis、队列或者副本,它禁止的是没有证据就添加它们。先用最简单的设计实现并测量,确认达不到已经批准的目标,再用数据说明新增构件解决哪一段瓶颈。这样写既保留性能底线,也避免 Constitution 变成一张技术产品采购单。
兼容性原则保护的是已经发出去的链接,只要链接仍然有效,兼容版本升级之后就必须继续按原契约跳转。公共 URL、存储映射或者响应契约发生破坏性变化,计划里必须同时给出迁移和回滚方案。对于普通 Web 应用,换一个返回字段也许只是前后端一起发版。对于短链接,旧地址可能已经印在海报、邮件和二维码里,破坏兼容就是永久损坏外部资产。
前面几条原则都在要求把主张落到具体依据上,这一组原则的最后一条针对方案本身。性能规则已经要求缓存和副本必须建立在测量之上,同样的思路适用于所有新增构件。新增的抽象、服务和持久化层必须解决当前已经记录的需求,不能只为"以后可能用到"预埋。最小设计并不等于代码越少越好,它指的是满足规格和前几条原则之后,构件数量与运维负担最低的方案。
五、运行 Constitution 命令
原则准备好之后,在项目根目录调用 /speckit.constitution。第 2 章已经说明,不同 Agent 的具体调用语法略有差别,本书继续用统一的斜杠命令表示。给短链接项目的输入不必把全文一次写完,但要把不可协商的方向和项目背景说清楚。
text
/speckit.constitution
为短链接服务建立项目宪法。重点保护短码映射正确性、基于验收场景的测试、
URL 输入与管理操作的安全边界、不会泄露目标 URL 的可观测性、基于测量的
性能优化,以及已签发链接的向后兼容。默认选择满足要求的最简单设计。
命令不会创建功能,也不会顺手实现短链接服务。当前版本带有明确的 Scope Guard,用户输入里即使夹着"再帮我写删除接口",它也只能把这部分列为后续意图,建议交给 /speckit.specify,不能修改应用代码。一次命令只负责一件事,写或更新 .specify/memory/constitution.md。
执行时,Agent 先通过模板解析脚本寻找当前生效的 constitution-template。解析过程会合并项目覆盖、Preset 和 Extension 层,全部失败时命令必须停止,不能偷偷退回某一份看起来能用的模板。然后它读取已有 Constitution,保留仍然有效的项目值,替换形如 [PROJECT_NAME] 的占位符。
首次批准会得到 1.0.0。以后修宪按语义化版本管理,删除原则或者以不兼容方式重定义义务升 MAJOR,增加原则或实质扩展治理升 MINOR,只澄清措辞且不改变义务升 PATCH。批准日期保留最初采用 Constitution 的时间,最后修订日期在内容变化时更新。
短链接示例最终写入了六条核心原则,另外增加 Engineering Constraints、Development Workflow and Quality Gates 和 Governance 三部分。文件末尾的治理信息如下。
markdown
**Version**: 1.0.0 | **Ratified**: 2026-09-30 | **Last Amended**: 2026-09-30
文件顶部还有一段 HTML 注释形式的 Sync Impact Report,记录版本变化、增加和删除的原则、受影响章节以及遗留 TODO。它不是宪法正文,而是给人审查本次修订的临时材料。命令生成后应先检查这份报告,确认规则和影响符合预期。正式提交修订前可以删除报告,项目真正长期生效的是下面的原则和治理条款。本书的示例仓库保留了首次生成的报告,方便对照命令产物。
六、Constitution 如何影响后续产物
Constitution 写进仓库不代表它自动生效,关键要看后续命令在哪些位置读取它。当前 Spec Kit 1.0.x 不是把原则复制到每一份模板里,而是在命令运行时读取 .specify/memory/constitution.md。这样修宪之后,新一轮工作拿到的是当前规则,不会继续被模板里冻结的旧副本误导。
影响从 /speckit.specify 就开始了。生成 Feature Spec 时,命令会读取 Constitution,把项目级治理约束纳入规格生成。例如删除功能涉及管理操作,规格不能把匿名删除当成一个无关紧要的默认值,因为 Constitution 已经要求管理操作具备身份验证和授权。不过,规格仍然只写用户行为和安全结果,不会因为宪法提到日志就开始设计日志字段。
真正的硬门禁出现在 /speckit.plan。命令读取功能规格和 Constitution,把原则展开到 plan.md 的 Constitution Check。检查在 Phase 0 研究之前做一次,避免一个明显违宪的方向继续消耗设计成本。Phase 1 完成数据模型、接口契约和 quickstart 之后再做一次,因为很多冲突只有在设计落地时才看得见。
假设 Agent 为短链接跳转提出"每次访问都请求第三方安全服务,确认目标 URL 后再跳转"。第一次检查就会发现它违反"跳转路径不得执行无关外部网络请求"的原则。如果另一份设计把完整目标 URL 写入访问日志,第二次检查会在接口和可观测设计成形后拦住它。门禁不是在文档末尾打一个"已阅读"的勾,而是要写出每条相关原则的通过证据。
/speckit.tasks 会把已经通过门禁的计划拆成任务,原则通过计划中的测试策略、契约和约束继续向下传递。到了 /speckit.analyze,命令会直接读取 Constitution,提取原则名称和 MUST、SHOULD 规则,再交叉检查 spec.md、plan.md 和 tasks.md。违反 MUST 的问题会被定为 CRITICAL,不允许在分析报告里靠重新解释原则降级。如果规则本身需要改变,必须退出当前功能流程,单独修宪。
实现之后的 /speckit.converge 也把 Constitution 当作不可协商的依据。代码违反原则时,它会优先生成修复任务,而不是宣布功能已经收敛。于是约束链形成了闭环,立项时定原则,规格阶段避免产生违背原则的需求默认值,计划阶段两次过门禁,分析阶段检查产物一致性,实现后再拿代码对照规则。
七、Constitution Check 与复杂度例外
工程原则如果完全不允许例外,很容易被团队绕开。另一种极端也不好,只要写一句"情况特殊"就能跳过,Constitution 很快会退化成建议。Spec Kit 在 plan.md 里提供了 Complexity Tracking 表,目的不是给违宪行为补手续,而是强迫计划把额外复杂度的必要性和替代方案摆到台面上。
短链接项目的计划如果准备加入缓存层,不能只写"为了提高性能"。它要先给出已经批准的性能目标和可复现测量,再说明更简单的方案为什么失败。记录可以是下面这样。
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| 增加独立缓存层和失效协议 | 在约定负载下,数据库直读的跳转 p95 为 340 ms,超过 200 ms 门禁 | 索引优化后 p95 仍为 286 ms,读副本会增加更高运维成本且未解决热点读取 |
这行记录至少提供了三个评审抓手。性能目标是不是事先批准的,测量环境和数据能不能复现,缓存是不是比被拒绝的方案更合适。按照本项目宪法,例外还要有负责人,以及删除或重新评审的条件。比如业务量下降后移除缓存,或者三个月后按线上数据重新评估。
并不是每条原则都能靠这张表豁免。我们的治理规则把 Mapping Correctness、Behavior-Driven Verification、Secure Boundaries、Predictable Failures and Safe Observability 设为不可豁免,除非先单独修宪。性能和简化原则允许在证据充分时接受狭窄例外。可例外范围由 Constitution 统一规定,计划阶段只需要在允许的范围内提交证据,不必每次重新争论哪条原则可以让步。
如果一个计划必须不断填写复杂度例外,问题通常不在表格写得不够充分,而在方案和项目原则长期冲突。此时要么换方案,要么承认项目环境已经变化,发起一次正式修宪。最差的做法是保留一条所有功能都违反的原则,再让每份 plan.md 机械复制同一段理由。
八、编写项目宪法的常见误区
第一种误区是把愿景当规则。"保持高质量"、"重视用户体验"、"系统必须安全稳定"没有反对者,也没有检查方法。它们给 Agent 的信息量接近零。改写时要找到具体失败模式,谁会看到什么错误,哪类数据不能出现在哪里,哪个契约在什么条件下必须保持。
第二种误区是把所有技术选择都当成同一层约束。对于已经确定技术基线的项目,要求后端继续使用 Python 和 FastAPI、数据继续保存在 SQLite,完全可以写进 Constitution。这样能防止 Agent 为一个小功能擅自引入另一套框架或存储。需要避免的是把每个功能内部的实现选择都提前固化,例如某次查询必须使用哪种缓存、某张表必须采用什么索引。项目级且长期稳定的技术边界放进 Constitution,当前功能的具体选型放进 plan.md,精确依赖版本则由项目清单和锁文件管理。
第三种误区是把功能需求塞进项目原则。"短链接支持自定义别名"、"管理页显示最近 30 天访问量"都只影响特定功能,应当进入对应 spec.md。Constitution 可以规定统计数据不得泄露完整目标 URL,也可以规定删除链接时必须说明派生数据如何处理,但不负责决定统计页面有哪些图表。
第四种误区是追求看起来严格的数字。100% 测试覆盖率、所有请求低于 100 毫秒、零错误率都很有气势,却可能没有风险依据和测量上下文。数字不是越硬越好,真正有用的门禁必须说明对象、环境和失败后果。当前资料不足以确定阈值时,要求计划补测量方案,比在宪法里编一个精确数字更诚实。
第五种误区是原则越多越放心。几十条规则会互相重复甚至冲突,Agent 每次都要花大量上下文解释,评审者也很难记住真正的红线。先保留少量高影响原则,把能由 linter、类型系统、测试框架和 CI 自动执行的细节交给工具。Constitution 管的是工具之外仍需要工程判断的边界。
最后一种误区是频繁修宪来迁就当前方案。计划一旦违反原则,就把原则改松,等于在比赛中移动球门。合理顺序应该是先调整 spec、plan 或 tasks,确认原则确实不再适合项目时,才发起独立修订,说明影响和迁移成本。语义化版本和 Sync Impact Report 的意义就在这里,它们让规则变化本身也接受审查。
九、小结
这一章给短链接项目补上了唯一一份跨功能共享的治理产物。我们没有写"高质量、安全、高性能"这类口号,而是从已经出现过的风险出发,建立了映射正确性、行为驱动验证、安全边界、可预测失败与安全可观测、可测量性能、兼容与简化六条原则。完整文件已经落在 code/shortlink/.specify/memory/constitution.md,首次批准版本为 1.0.0。
这份 Constitution 的价值不在于多了一篇 Markdown。/speckit.specify 会读取治理约束,/speckit.plan 在研究前和设计后执行两次检查,/speckit.analyze 把违反 MUST 的冲突定为 CRITICAL,/speckit.converge 再把实现层的违宪问题转成修复任务。确实需要额外复杂度时,plan.md 必须留下测量证据、被否决的简单方案和复查条件,例外不能靠一句"情况特殊"溜过去。
项目的工程底线现在有了稳定落点。下一章回到单个功能,用 /speckit.specify 写第一份真正可执行的 Feature Spec。Constitution 负责规定无论做什么都不能越过的边界,spec.md 则要把这一次究竟做什么、怎样算完成说清楚。