diff --git a/.gitignore b/.gitignore index 37f187e..c962ed6 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ tocodex-docs/ # this private tree and must never be allowlisted — tracked files bypass these # rules until `git rm --cached`. /docs/** +!/docs/AGENTS.md !/docs/lua-configuration.md !/docs/math-rendering-test.md !/docs/chemistry-rendering-test.md diff --git a/AGENTS.md b/AGENTS.md index 653ed2e..c7d37df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,32 +1,21 @@ # Nebula 项目代理指引 -## 强制工程规范 +## 全局规则 - 修改前阅读 `CONTRIBUTING.md`、`docs/architecture.md` 和 `docs/project-constraints.md`;新 UI 文案同时遵循 `docs/internationalization.md`。 +- 规则按目录分层:先遵守本文件,再读取目标文件路径上最近的 `AGENTS.md`。模块细节留在模块目录,不回填到根规则。 - 行数预算与依赖方向由 `architecture/` 声明,使用 `python3 scripts/check_architecture.py --base ` 验证;800 行仅提示,2000 行是既有防灾上限。 - 禁止靠抬高预算、删测试、压缩格式、机械分片或静默排除目录让门禁通过。规范存在缺陷时,先给可复现反例,补正反测试并记录经维护者审查的规则修订。 - 核心规则保持单一权威实现;按职责和生命周期拆分,不因文件夹名称或历史模块别名推断耦合。新增长期依赖、跨层接口、持久化和线程模型变更须给出设计依据。 - 普通翻译查询维持已测零分配合同;参数格式化和冷路径另行评估。不能把单机微基准写成普遍性能保证。 - 不得声称仅加入 workflow 或 `CODEOWNERS` 就已启用 GitHub 强制保护;服务端设置及负例 PR 验证须另行获准并核验。 +- `docs/` 记录经过核验的当前事实;非平凡改动的决策、被否定方案和事故因果写入与代码职责同路径的 `architecture/notes/`。遵循 [note 规则](architecture/notes/AGENTS.md),普通样式修改和单文件修复不写 note。 -## 发布任务的持久化上下文 +## 分层入口 -- 处理版本号、构建、打包、自动更新、GitHub Release、Changelog 或 Release Note 前,必须先完整阅读 [`.agents/memory.md`](.agents/memory.md) 的发布章节。 -- `.agents/memory.md` 是经过实际发布核验的项目记忆,不是临时推理草稿。只有在代码、脚本、GitHub 元数据或真实运行结果已经核实后才能更新;过时结论必须直接修正并注明新的核验日期。 -- 发布相关命令必须使用 UTF-8。工作区可能包含用户的未跟踪探针、构建目录和截图,只能显式暂存本次文件,不得用清理、全量暂存或重置命令处理它们。 - -## Release Note 原则 - -1. 发布说明只写已经实现并有代码、构建或真实运行证据的用户可感知变化。调试探针、测试用强制弹窗、内部重构和未经验证的推断不得写成已交付功能。 -2. 沿用 `v1.3.0` 的双语结构:English 的 `Added / Fixed / Improved`、中文的 `新增 / 修复 / 改进`、`Contributors` 和最终资产 `SHA256`。中英文必须表达同一事实,不能一侧增加能力或承诺。 -3. 同一发布的 `CHANGELOG.md`、`docs/release-notes/vX.Y.Z.md` 和 GitHub Release 正文必须同步。应在正式构建前完成 Changelog,因为 ZIP 和安装器会把它打进产物。 -4. Issue 引用必须先读取对应 Issue 的标题、正文、状态和必要评论,再挂到最准确的一条说明上。禁止按编号猜测、复用旧版本链接,或把宽泛需求说成逐项 Bug 报告。 -5. 英文引用使用 `Addresses [#N](https://github.com/Kuddev/nebula/issues/N).`,中文使用 `对应 [#N](https://github.com/Kuddev/nebula/issues/N)。`。一个 Issue 涵盖多项紧密修复时,优先只在最具代表性的条目引用一次。 -6. 文案以用户结果为主,例如“100% 不透明度不再回到 82%”,不要用内部类型名、辅助函数或提交标题代替行为说明。仍开放或只部分覆盖的问题不得宣称彻底解决。 - -## 发布安全边界 - -- 正式发布不得使用 `-SkipBuild` 或 `-AllowStale` 绕过新鲜度检查,也不得把 workspace 默认构建产生的 legacy shell 当作 GPUI 产品包。 -- 不得因为某个磁盘空间更大就擅自使用它。构建和输出目录必须位于用户明确允许的路径;当前环境尤其不得操作 `E:\`。 -- GitHub 推送使用普通非强制推送。发布后若只有说明文档修订,可同步分支和 Release 正文,但不得擅自移动已经对应二进制的版本标签。 -- 发布结束前必须核验 Release 标题、标签、正文、资产真实文件名、空资产标签、大小、SHA256、分支与版本标签指向;不能只以创建命令返回成功作为完成依据。 +- 应用、窗口和 UI:遵循 [`nebula_app/AGENTS.md`](nebula_app/AGENTS.md)。 +- 终端核心、PTY、VT 和输入:遵循 [`nebula_terminal/AGENTS.md`](nebula_terminal/AGENTS.md)。 +- 设置、持久化和语言注册:遵循 [`nebula_settings/AGENTS.md`](nebula_settings/AGENTS.md)。 +- 检查器、门禁和治理脚本:遵循 [`scripts/AGENTS.md`](scripts/AGENTS.md)。 +- 文档事实与操作手册:遵循 [`docs/AGENTS.md`](docs/AGENTS.md)。 +- 版本号、构建、打包、自动更新、GitHub Release、Changelog 或 Release Note:先完整阅读 [`packaging/AGENTS.md`](packaging/AGENTS.md)、[`docs/release-notes/AGENTS.md`](docs/release-notes/AGENTS.md) 和本地 `.agents/memory.md` 的发布章节。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f568c83..9fb8726 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,8 @@ when changing them. Internal Rust crate and source-directory names remain stable - [Enforced contracts and review rules](docs/project-constraints.md) - UI changes must also satisfy the **UI 设计约束 / UI design constraints** chapter in that document: interaction states, result feedback, hit targets and visual acceptance. -- [Decision log and policy change process](docs/architecture-decisions.md) +- [Causal note policy](architecture/notes/AGENTS.md) for new non-trivial decisions +- [Legacy decision archive](docs/architecture-decisions.md) for accepted historical context - [Evidence behind the rules](docs/engineering-evidence.md) - [Internationalization](docs/internationalization.md) when changing UI text @@ -69,7 +70,8 @@ raise any existing tracked private artifact for an explicit maintainer decision. 1. State the user-visible problem and the behavior to preserve. Discuss a new cross-layer dependency, persisted format, core abstraction or threading model - before implementing it. Record significant decisions in the decision log. + before implementing it. Record significant decisions under the owning path in + `architecture/notes/`; ordinary fixes do not create a note. 2. Keep one conceptual change per PR. A necessary extraction and its behavior tests may accompany the feature; unrelated rewrites and formatting may not. 3. Put shared rules in their existing authority. UI modules adapt those rules; diff --git a/architecture/notes/AGENTS.md b/architecture/notes/AGENTS.md new file mode 100644 index 0000000..4863be9 --- /dev/null +++ b/architecture/notes/AGENTS.md @@ -0,0 +1,35 @@ +# Causal note rules + +## Purpose + +- `docs/` describes verified current behavior, architecture and operating procedures. +- `architecture/notes/` records why a non-trivial decision exists: context, rejected alternatives, consequences, evidence and replacement conditions. +- Plans, task lists and temporary work logs do not belong here. + +## When to write + +Write a note for dependency direction, core ownership, persistent formats, protocols, threading/lifetime, important performance contracts, governance changes or a cross-layer incident whose cause is likely to be forgotten. Do not write one for styling, ordinary single-file fixes, mechanical refactors or routine dependency updates. + +## Location and history + +- Mirror the owning code path, for example `architecture/notes/nebula_terminal/input/2026-09-19-example.md`. +- Use `YYYY-MM-DD-kebab-case.md`. Keep one decision or incident per file. +- Do not create a global `INDEX.md`. The nearest module `AGENTS.md` points agents to the relevant directory. +- Once accepted, do not rewrite the old rationale into a new conclusion. Create a new note with `Supersedes`; the old note may receive only a short `Superseded by` pointer. +- Notes are reviewed repository content and contain only rationale and evidence intended for repository review. + +## Required structure + +Each decision note stays at or below 200 physical lines and contains: + +1. `Status` +2. `Context` +3. `Evidence` +4. `Decision` +5. `Rejected alternatives` +6. `Consequences` +7. `Validation` +8. `Supersedes` +9. `Revisit when` + +State `None` where a relation does not exist. Link authoritative code, tests or factual docs instead of copying API inventories. Tests protect current behavior; the note preserves the reason and the discarded paths. diff --git a/architecture/notes/governance/2026-09-19-layered-rules-and-causal-notes.md b/architecture/notes/governance/2026-09-19-layered-rules-and-causal-notes.md new file mode 100644 index 0000000..7591be7 --- /dev/null +++ b/architecture/notes/governance/2026-09-19-layered-rules-and-causal-notes.md @@ -0,0 +1,81 @@ +# Layered agent rules and causal notes + +- Date: 2026-09-19 +- Scope: repository governance and agent context +- Owner: maintainer review + +## Status + +Accepted by direct maintainer instruction; implementation is included with this note. + +## Context + +The repository had one root `AGENTS.md`. Release-note formatting, release safety, +architecture gates, UI behavior and terminal-core constraints were all loaded for +every task. Verified facts, a consolidated ADR log, plans, temporary work logs and +hard lessons also shared the broad `docs/` area despite different lifecycles. + +## Evidence + +- Only the root `AGENTS.md` existed before this change. +- `docs/architecture-decisions.md` had grown into one consolidated decision file. +- Local planning, handoff and hard-lesson files used `docs/` despite having causal + or temporary lifecycles. +- Existing local-only agent context already followed a separate lifecycle outside + the reviewed engineering documents. +- Existing architecture contracts already required non-trivial decisions, + alternatives, consequences, validation and revisit conditions. + +## Decision + +Keep the root guide limited to repository-wide invariants and task entry points. +Place responsibility-specific rules in the nearest owning directory. Split release +build safety from release-note wording. Store sanitized, reviewable causal records +under `architecture/notes/`, mirroring the owning code path and without a global +index. Local-only operational context keeps its existing lifecycle. + +Current factual documents continue to update in place. Accepted decision notes keep +their original rationale; a later design creates a new note and declares the +supersession relationship. Ordinary fixes and styling changes do not create notes. + +The existing consolidated ADR and hard-lesson files remain historical material. +They are not bulk-rewritten or mechanically split; relevant history migrates only +when a future non-trivial change needs a new note. + +## Rejected alternatives + +- Keep adding every rule to root `AGENTS.md`: unrelated tasks continue paying the + context cost and module-specific instructions remain harder to discover. +- Rewrite all historical ADRs immediately: this creates a large conflict-prone + migration and risks changing the meaning of accepted decisions. +- Keep every causal note local and ignored: collaborators and clean clones lose the + reviewed reasoning behind shared code. +- Maintain one global notes index: parallel branches must edit the same file for + unrelated decisions, creating predictable merge conflicts. +- Put every plan and small fix in notes: the directory becomes a chronological + activity log instead of a durable causal record. + +## Consequences + +Agents must read the root guide plus the nearest module guide. New modules add an +`AGENTS.md` only when they have distinct contracts. Notes contain only information +intended for repository review. The old ADR log stays readable but is no longer the +required destination for new decisions. + +## Validation + +Governance tests verify that layered guides and the note policy are tracked, +decision notes use dated module paths, contain all required sections, stay within +the scoped line limit and do not introduce a global index. Positive and negative +note fixtures lock the checker behavior. + +## Supersedes + +Supersedes the storage process in the `Process` section of the consolidated +`docs/architecture-decisions.md`; it does not rewrite or invalidate ADR-0001 onward. + +## Revisit when + +Revisit the 200-line note limit only with a reproducible legitimate note that cannot +remain clear through links or a scoped evidence file. Revisit directory ownership +when a module boundary changes in `docs/architecture.md`. diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 0000000..9671e01 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,8 @@ +# Documentation rules + +- `docs/` 记录经过核验的当前事实:系统行为、架构地图、协议、操作手册、验收合同和事故复盘的事实部分。 +- 非平凡决策的原因、被否定方案和替代关系写入 `architecture/notes/`;计划、待办和临时工作记录不作为当前事实文档新增。 +- 事实变化时更新正文;需要表达演进时写明版本、日期或状态,避免无锚点的“以前、现在、不再”。 +- 不手抄可从 schema、类型定义、命令帮助或生成文件直接得到的 API 清单;链接单一权威来源。 +- 每篇文档保持单一职责和可审查长度。测试语料、发布说明和验收材料按各自用途管理,不套用普通事实文档的机械上限。 +- `release-notes/` 还必须遵循该目录更具体的 `AGENTS.md`。 diff --git a/docs/architecture-decisions.md b/docs/architecture-decisions.md index 8227723..055d06e 100644 --- a/docs/architecture-decisions.md +++ b/docs/architecture-decisions.md @@ -1,5 +1,11 @@ # Architecture decisions / 架构决策记录 +> Historical consolidated archive. New non-trivial decisions after the 2026-09-19 +> governance migration live under the owning path in +> [`architecture/notes/`](../architecture/notes/AGENTS.md). Existing ADRs keep their +> accepted rationale; a replacement creates a new note with `Supersedes` rather than +> rewriting the old conclusion. + ## Process Record decisions that change dependency direction, core ownership, persistent @@ -12,6 +18,8 @@ Revisit condition.** Identify the accountable maintainer in the PR review. A pol change must include a failing legitimate example when correcting a false positive, plus a violation that must remain rejected. Do not use a policy edit to conceal an unrelated feature's growth. Remote approval/enforcement is not implied by this log. +The note location and supersession rules above replace only this section's former +single-file storage process; the records below remain unchanged historical context. ## ADR-0001 — Evidence-based architecture contracts diff --git a/docs/architecture.md b/docs/architecture.md index d144968..1b7358d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -88,7 +88,8 @@ whole product. direction, cost, ownership and why an existing module is insufficient. See [CONTRIBUTING](../CONTRIBUTING.md), [enforced contracts](project-constraints.md), -and [decision records](architecture-decisions.md). Architectural review evaluates +the [causal note policy](../architecture/notes/AGENTS.md), and the +[legacy decision archive](architecture-decisions.md). Architectural review evaluates cohesion and knowledge shared across boundaries; CI provides narrower mechanical checks, not a guarantee against every future design mistake. diff --git a/docs/project-constraints.md b/docs/project-constraints.md index 9d902dd..b52f628 100644 --- a/docs/project-constraints.md +++ b/docs/project-constraints.md @@ -94,11 +94,15 @@ a failing violation, and fixtures for known false positives. Guardrail bugs are bugs: fix them before calling the gate mandatory. A failing architecture check must not be ignored, but a demonstrably defective policy must be revisable. -Record important changes in [the decision log](architecture-decisions.md): context, -evidence, alternatives, consequences, validation and a replacement/removal condition. -Ordinary fixes do not need ceremonial ADRs. A change to a budget or rule is a -dedicated governance change, not an unexplained edit hidden inside a feature PR. -No automatic exception-adding or budget-increasing command is provided. +Record important changes under the owning path in +[`architecture/notes/`](../architecture/notes/AGENTS.md): context, evidence, rejected +alternatives, consequences, validation and a replacement/removal condition. Ordinary +fixes do not need ceremonial records. The consolidated +[decision log](architecture-decisions.md) remains the historical archive through the +governance migration; new conclusions supersede old records instead of rewriting +their rationale. A change to a budget or rule is a dedicated governance change, not +an unexplained edit hidden inside a feature PR. No automatic exception-adding or +budget-increasing command is provided. Emergency/security repairs must not be forced into a dangerous broad refactor just to preserve a flawed metric. Escalate the demonstrated conflict to a maintainer, @@ -109,7 +113,8 @@ Existing platform-cfg counting is a historical heuristic, not a proof of platfor decoupling. It is not promoted to this new gate: its comment/string/nesting behavior needs separate work. Fork patches retain exact-revision pins, thin adapter scope, recorded motivation and an upstream-removal condition. Release safety remains in -`AGENTS.md` and the existing verified release instructions. +the root task entry, `packaging/AGENTS.md`, `docs/release-notes/AGENTS.md` and the +existing verified release instructions. ## 5. UI 设计约束 / UI design constraints diff --git a/docs/release-notes/AGENTS.md b/docs/release-notes/AGENTS.md new file mode 100644 index 0000000..84bf8cd --- /dev/null +++ b/docs/release-notes/AGENTS.md @@ -0,0 +1,9 @@ +# Release Note rules + +1. 只写已实现且有代码、构建或真实运行证据的用户可感知变化。调试探针、测试用强制弹窗、内部重构和未经验证的推断不得写成已交付功能。 +2. 沿用 `v1.3.0` 的双语结构:English 的 `Added / Fixed / Improved`、中文的 `新增 / 修复 / 改进`、`Contributors` 和最终资产 `SHA256`。中英文表达同一事实。 +3. 同一发布的 `CHANGELOG.md`、`docs/release-notes/vX.Y.Z.md` 和 GitHub Release 正文保持同步。正式构建前完成 Changelog,因为 ZIP 和安装器会把它打进产物。 +4. 引用 Issue 前读取其标题、正文、状态和必要评论,再挂到最准确的一条说明上。不得按编号猜测、复用旧版本链接或把宽泛需求改写成逐项 Bug 报告。 +5. 英文使用 `Addresses [#N](https://github.com/Kuddev/nebula/issues/N).`,中文使用 `对应 [#N](https://github.com/Kuddev/nebula/issues/N)。`。同一 Issue 涵盖紧密相关修复时,优先只在代表性条目引用一次。 +6. 文案描述可核验的用户结果,不用内部类型名、辅助函数或提交标题代替行为说明。仍开放或只部分覆盖的问题必须准确标明范围,不能扩大完成度。 +7. Contributors 规则和已发布版本的例外以本地 `.agents/memory.md` 中经过核验的最新发布结论为准,不凭空补头像或贡献者。 diff --git a/nebula_app/AGENTS.md b/nebula_app/AGENTS.md new file mode 100644 index 0000000..e4d2e54 --- /dev/null +++ b/nebula_app/AGENTS.md @@ -0,0 +1,9 @@ +# Nebula application rules + +- 本目录是应用编排和适配层;依赖方向保持为 UI/组合 → 应用能力 → 共享领域规则,不在视图里复制设置、持久化、终端状态或协议状态机。 +- `src/gpui_shell` 负责 GPUI 呈现、命令和订阅。交互改动必须覆盖默认、hover、pressed、键盘焦点、disabled/loading 以及成功/失败中实际需要的状态。 +- 操作形成“触发 → 执行 → 可感知结果”的闭环。成功只能在操作真实完成或底层接口明确接受后显示,不能把回调触发当成平台级成功。 +- 异步工作必须有所有者、取消/过期策略和视图销毁清理;旧结果不得覆盖新状态。渲染回调不得同步等待磁盘、网络、子进程或长锁。 +- 新 UI 文案使用现有 typed message ID、命名占位符和回退合同,遵循 [`../docs/internationalization.md`](../docs/internationalization.md)。 +- 测试真实控件和布局路径;编译或状态单测不能代替 hover、命中区域、键盘路径和视觉验收。 +- 只有跨层所有权、持久化、协议、线程/生命周期或重要性能取舍才写 note,路径镜像到 `architecture/notes/nebula_app//`。 diff --git a/nebula_settings/AGENTS.md b/nebula_settings/AGENTS.md new file mode 100644 index 0000000..8941d87 --- /dev/null +++ b/nebula_settings/AGENTS.md @@ -0,0 +1,7 @@ +# Settings core rules + +- 本目录拥有设置模型、默认值、验证、兼容读取、语言注册和持久化合同,不拥有 UI 控件或应用编排。 +- 保持零生产依赖合同。新依赖、动态解析或后台服务需要单独设计依据,不能为了一个界面入口扩大核心职责。 +- 新设置必须定义默认值、合法范围、序列化/反序列化、旧配置兼容、重置和往返测试;开放会话是否即时生效必须明确。 +- 语言和消息注册保持单一权威;普通翻译查询的既有零分配合同不能被格式化或冷路径逻辑悄悄改变。 +- 持久化格式、兼容迁移或共享设置所有权的非平凡变化写入 `architecture/notes/nebula_settings//`。 diff --git a/nebula_terminal/AGENTS.md b/nebula_terminal/AGENTS.md new file mode 100644 index 0000000..4aa27ce --- /dev/null +++ b/nebula_terminal/AGENTS.md @@ -0,0 +1,8 @@ +# Terminal core rules + +- 本目录只拥有 grid、VT、PTY、输入编码、回放和终端行为;不得依赖产品视图、窗口状态或 GPUI 组件。 +- 输入修复必须分别验证原生控制台读取方、翻译后字节流读取方和协商协议;不能用单一路径正常推断所有客户端兼容。 +- 字节流、解析器和回放测试要覆盖分块边界、静默源、错误/EOF 和平台差异。间歇问题需要能稳定失败的判据。 +- 热路径变更提供代表性成本证据;分配、吞吐或内存结论限定到实际测量的内容、尺寸、平台和构建配置。 +- 不为 UI 需求在终端核心增加产品概念。确需新接口时,公开最小命令/结果并保持状态私有。 +- 重大协议、缓冲、线程、所有权或兼容性决策写入 `architecture/notes/nebula_terminal//`;普通回归修复只需测试和变更说明。 diff --git a/packaging/AGENTS.md b/packaging/AGENTS.md new file mode 100644 index 0000000..af7d031 --- /dev/null +++ b/packaging/AGENTS.md @@ -0,0 +1,10 @@ +# Build, packaging and release rules + +- 处理版本号、构建、打包、自动更新、GitHub Release、Changelog 或 Release Note 前,完整阅读本地 `.agents/memory.md` 的发布章节和 `docs/release-notes/AGENTS.md`。 +- `.agents/memory.md` 是经真实发布核验的本地记忆,不是临时推理草稿。只有代码、脚本、GitHub 元数据或真实运行结果已核实后才能更新;过时结论直接修正并注明核验日期。 +- 所有发布相关命令使用 UTF-8。工作区可能包含用户的未跟踪探针、构建目录和截图,只能显式暂存本次文件,不得清理、全量暂存或重置它们。 +- 正式发布不得使用 `-SkipBuild` 或 `-AllowStale` 绕过新鲜度检查,也不得把 workspace 默认构建产生的 legacy shell 当作 GPUI 产品包。 +- 构建和输出目录必须位于用户明确允许的路径。 +- GitHub 推送使用普通非强制推送。发布后二进制版本标签不可因说明文档修订而移动。 +- 结束前核验 Release 标题、标签、正文、真实资产文件名、空资产标签、大小、SHA256、分支与版本标签指向;创建命令成功不等于发布核验完成。 +- 构建图、资产集合、更新协议或发布事务的非平凡变化写入 `architecture/notes/packaging/`。 diff --git a/scripts/AGENTS.md b/scripts/AGENTS.md new file mode 100644 index 0000000..c909599 --- /dev/null +++ b/scripts/AGENTS.md @@ -0,0 +1,8 @@ +# Maintained scripts and governance rules + +- 维护脚本是产品工程合同的一部分:默认离线、UTF-8、确定性,并对缺失输入、解析失败、读取失败和不完整结果 fail closed。 +- 新门禁必须说明不变量,提供一个合法通过样例、一个违规失败样例和已知误报的反例测试;先修门禁缺陷,再把它称为强制规则。 +- 不通过提高预算、扩大排除、自动加例外、删除测试或 `continue-on-error` 消除失败。规则错误时提交窄范围政策修订和回归。 +- 依赖和行数策略的单一权威仍在 `architecture/`;脚本读取同一来源,不复制第二份常量清单。 +- 发布脚本还必须遵循 [`../packaging/AGENTS.md`](../packaging/AGENTS.md)。 +- 门禁语义、扫描边界或治理流程的非平凡变化写入 `architecture/notes/scripts//`。 diff --git a/scripts/tests/test_architecture_governance.py b/scripts/tests/test_architecture_governance.py index 1bdd04e..e16ba87 100644 --- a/scripts/tests/test_architecture_governance.py +++ b/scripts/tests/test_architecture_governance.py @@ -8,8 +8,37 @@ ROOT = Path(__file__).resolve().parents[2] DOCUMENTS = ( "CONTRIBUTING.md", "docs/project-constraints.md", "docs/architecture.md", "docs/architecture-decisions.md", "docs/engineering-evidence.md", - "docs/internationalization.md", + "docs/internationalization.md", "architecture/notes/AGENTS.md", ) +AGENT_GUIDES = ( + "AGENTS.md", "nebula_app/AGENTS.md", "nebula_terminal/AGENTS.md", + "nebula_settings/AGENTS.md", "scripts/AGENTS.md", "packaging/AGENTS.md", + "docs/AGENTS.md", "docs/release-notes/AGENTS.md", +) +PUBLIC_GUIDES = DOCUMENTS + AGENT_GUIDES +NOTES_ROOT = ROOT / "architecture/notes" +NOTE_REQUIRED_SECTIONS = ( + "Status", "Context", "Evidence", "Decision", "Rejected alternatives", + "Consequences", "Validation", "Supersedes", "Revisit when", +) +MAX_NOTE_LINES = 200 + + +def decision_note_errors(text): + errors = [] + if not re.search(r"(?m)^# [^#\n]+$", text): + errors.append("missing one level-one title") + headings = { + match.group(1).strip().casefold() + for match in re.finditer(r"(?m)^##\s+(.+?)\s*$", text) + } + for section in NOTE_REQUIRED_SECTIONS: + if section.casefold() not in headings: + errors.append(f"missing section: {section}") + line_count = len(text.splitlines()) + if line_count > MAX_NOTE_LINES: + errors.append(f"{line_count} lines exceeds {MAX_NOTE_LINES}") + return errors class GovernanceTests(unittest.TestCase): @@ -30,10 +59,13 @@ class GovernanceTests(unittest.TestCase): self.assertEqual(self.ignored_paths(paths), set(ignored)) def test_contributor_documents_exist_and_are_not_ignored(self): - for name in DOCUMENTS: + for name in PUBLIC_GUIDES: with self.subTest(name=name): self.assertTrue((ROOT / name).is_file()) - self.assertEqual(self.ignored_paths(DOCUMENTS), set(), "public contributor docs must not be ignored") + self.assertEqual( + self.ignored_paths(PUBLIC_GUIDES), set(), + "public contributor and agent guides must not be ignored", + ) def test_private_investigations_remain_ignored(self): paths = ( @@ -72,7 +104,7 @@ class GovernanceTests(unittest.TestCase): def test_maintained_tests_dependencies_and_public_assets_stay_visible(self): paths = ( - *DOCUMENTS, ".github/CODEOWNERS", ".github/PULL_REQUEST_TEMPLATE.md", + *PUBLIC_GUIDES, ".github/CODEOWNERS", ".github/PULL_REQUEST_TEMPLATE.md", ".github/workflows/architecture.yml", "architecture/dependencies.toml", "architecture/file-budgets.txt", "scripts/check_architecture.py", "scripts/tests/test_architecture_governance.py", "scripts/tests/new_regression.py", @@ -84,11 +116,12 @@ class GovernanceTests(unittest.TestCase): "third_party/winit-0.30.13/src/lib.rs", "docs/screenshots/SHOTLIST.md", "docs/screenshots/hero.png", "docs/release-notes/v1.5.0.md", "docs/skills/pebrel-runtime/SKILL.md", + "architecture/notes/nebula_terminal/input/2026-09-19-example.md", ) self.assertEqual(self.ignored_paths(paths), set()) def test_relative_document_links_resolve(self): - for name in DOCUMENTS: + for name in PUBLIC_GUIDES: document = ROOT / name text = document.read_text(encoding="utf-8") for link in re.findall(r"\]\(([^)]+)\)", text): @@ -98,6 +131,47 @@ class GovernanceTests(unittest.TestCase): with self.subTest(document=name, target=target): self.assertTrue((document.parent / target).exists()) + def test_decision_note_contract_accepts_and_rejects_known_examples(self): + valid = "# Decision\n\n" + "\n\n".join( + f"## {section}\n\nRecorded." for section in NOTE_REQUIRED_SECTIONS + ) + self.assertEqual(decision_note_errors(valid), []) + + missing_alternative = valid.replace("## Rejected alternatives", "## Options") + self.assertIn( + "missing section: Rejected alternatives", + decision_note_errors(missing_alternative), + ) + + oversized = valid + "\n" + "\n".join("detail" for _ in range(MAX_NOTE_LINES)) + self.assertTrue( + any("exceeds" in error for error in decision_note_errors(oversized)), + "an oversized note must fail", + ) + + def test_reviewed_decision_notes_are_scoped_and_complete(self): + notes = [ + path for path in NOTES_ROOT.rglob("*.md") + if path.name != "AGENTS.md" + ] + self.assertTrue(notes, "the governance migration must include its decision note") + self.assertFalse( + any(path.name.casefold() == "index.md" for path in NOTES_ROOT.rglob("*.md")), + "decision notes must not use a global index", + ) + for path in notes: + relative = path.relative_to(NOTES_ROOT) + with self.subTest(note=relative.as_posix()): + self.assertGreaterEqual(len(relative.parts), 2, "notes must follow an owner path") + self.assertRegex( + path.name, + r"^\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*\.md$", + ) + self.assertEqual( + decision_note_errors(path.read_text(encoding="utf-8")), + [], + ) + def test_required_job_is_unfiltered_and_unprivileged(self): workflow = (ROOT / ".github/workflows/architecture.yml").read_text(encoding="utf-8") for required in (" pull_request:", " merge_group:", "contents: read",