atlas-project-harness 0.2.0-beta.2 → 0.2.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +267 -0
- package/DESIGN.md +46 -3
- package/README.md +24 -14
- package/_support/__pycache__/ac_coverage.cpython-312.pyc +0 -0
- package/_support/__pycache__/case_block.cpython-312.pyc +0 -0
- package/_support/__pycache__/layout.cpython-312.pyc +0 -0
- package/_support/__pycache__/manual_block.cpython-312.pyc +0 -0
- package/_support/__pycache__/stack_profile.cpython-312.pyc +0 -0
- package/_support/ac_coverage.py +120 -0
- package/_support/case_block.py +3 -3
- package/_support/layout.py +411 -0
- package/_support/manual_block.py +67 -0
- package/_support/stack_profile.py +3 -1
- package/adapters/__pycache__/_graph.cpython-312.pyc +0 -0
- package/adapters/__pycache__/_jpa.cpython-312.pyc +0 -0
- package/adapters/__pycache__/_lib.cpython-312.pyc +0 -0
- package/adapters/__pycache__/api.cpython-312.pyc +0 -0
- package/adapters/__pycache__/models.cpython-312.pyc +0 -0
- package/adapters/__pycache__/tests.cpython-312.pyc +0 -0
- package/adapters/_graph.py +7 -2
- package/adapters/_jpa.py +291 -0
- package/adapters/_lib.py +8 -3
- package/adapters/ac/__init__.py +80 -0
- package/adapters/ac/__pycache__/__init__.cpython-312.pyc +0 -0
- package/adapters/ac/__pycache__/command.cpython-312.pyc +0 -0
- package/adapters/ac/__pycache__/js.cpython-312.pyc +0 -0
- package/adapters/ac/__pycache__/junit.cpython-312.pyc +0 -0
- package/adapters/ac/__pycache__/pytest.cpython-312.pyc +0 -0
- package/adapters/ac/command.py +88 -0
- package/adapters/ac/js.py +334 -0
- package/adapters/ac/junit.py +266 -0
- package/adapters/ac/pytest.py +380 -0
- package/adapters/api.py +331 -8
- package/adapters/models.py +126 -2
- package/adapters/routes.py +7 -3
- package/adapters/tests.py +43 -5
- package/apply/reference.md +6 -3
- package/bin/atlas.js +66 -28
- package/extensions/pi.ts +39 -8
- package/install.sh +179 -19
- package/package.json +1 -1
- package/patches/project-wiring/README.md +4 -3
- package/patches/project-wiring/__pycache__/apply-wiring.cpython-312.pyc +0 -0
- package/patches/project-wiring/agent-pointer.md +1 -1
- package/patches/project-wiring/apply-wiring.py +146 -153
- package/patches/workflow-plan-apply/1.6-plan-apply.insert.md +1 -1
- package/rings/e2e/reference.md +37 -20
- package/rings/prd/reference.md +26 -8
- package/rings/structure/reference.md +3 -2
- package/scripts/__pycache__/atlas_check.cpython-312.pyc +0 -0
- package/scripts/__pycache__/atlas_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/atlas_verify.cpython-312.pyc +0 -0
- package/scripts/__pycache__/closeout_derive.cpython-312.pyc +0 -0
- package/scripts/__pycache__/e2e_console.cpython-312.pyc +0 -0
- package/scripts/__pycache__/gen_e2e_scripts.cpython-312.pyc +0 -0
- package/scripts/__pycache__/gen_testid_stub.cpython-312.pyc +0 -0
- package/scripts/__pycache__/make_review_inputs.cpython-312.pyc +0 -0
- package/scripts/__pycache__/pkg_digest.cpython-312.pyc +0 -0
- package/scripts/__pycache__/refresh_structure.cpython-312.pyc +0 -0
- package/scripts/__pycache__/release.cpython-312.pyc +0 -0
- package/scripts/__pycache__/write_claim.cpython-312.pyc +0 -0
- package/scripts/atlas_check.py +329 -86
- package/scripts/atlas_report.py +10 -2
- package/scripts/atlas_verify.py +435 -0
- package/scripts/closeout_derive.py +296 -0
- package/scripts/e2e_console.py +46 -31
- package/scripts/gen_e2e_scripts.py +424 -66
- package/scripts/gen_testid_stub.py +8 -3
- package/scripts/make_review_inputs.py +20 -6
- package/scripts/pkg_digest.py +42 -5
- package/scripts/refresh_structure.py +29 -8
- package/scripts/release.py +238 -7
- package/scripts/write_claim.py +204 -0
- package/shared/closeout.md +7 -0
- package/shared/gates.md +24 -19
- package/shared/global-rules.md +8 -1
- package/shared/independent-review.md +4 -3
- package/shared/layout.md +28 -3
- package/shared/single-source.md +2 -0
- package/shared/stack-profile.md +19 -10
- package/skills/atlas-apply/SKILL.md +1 -0
- package/skills/atlas-design-review/SKILL.md +3 -3
- package/skills/atlas-e2e/SKILL.md +1 -0
- package/skills/atlas-prd/SKILL.md +1 -0
- package/skills/atlas-release/SKILL.md +9 -3
- package/skills/atlas-structure/SKILL.md +1 -0
- package/skills/frontend-design/SKILL.md +2 -2
- package/skills/frontend-design/references/tuning-verbs.md +1 -1
- package/templates/e2e/case-shard.md +1 -1
- package/templates/e2e/console/app.js +25 -4
- package/templates/e2e/console/console.css +2 -0
- package/templates/e2e/console/index.html +2 -2
- package/templates/prd/domain.md +14 -12
- package/templates/prd/global.md +2 -2
- package/templates/stack-profile.yaml +12 -11
- package/tests/__pycache__/test_ac_adapters.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_adapter_api.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_adapter_models.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_apply_wiring.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_atlas_check.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_atlas_verify.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_atlas_verify_playground.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_canonical_consistency.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_closeout_derive.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_duty_bug_families.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_e2e_console.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_gen_e2e_scripts.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_gen_e2e_scripts.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_install_selfcheck.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_layout.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_map_anchors.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_no_framework_names.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_project_wiring.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_refresh_facts.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_shared_kernel.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_tools.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_tools.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_validate_ac_alignment.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_validate_adr.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_validate_walkthrough.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_validators.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/__pycache__/test_write_claim.cpython-312-pytest-9.1.1.pyc +0 -0
- package/tests/fixtures/e2e_coverage_dims_golden.json +12 -0
- package/tests/fixtures/verify_playground/product/e2e/cases/home.md +40 -0
- package/tests/fixtures/verify_playground/product/e2e/e2e-index.md +18 -0
- package/tests/fixtures/verify_playground/product/e2e/reviews/2026-01-01-fixture/README.md +3 -0
- package/tests/fixtures/verify_playground/product/e2e/reviews/2026-01-01-fixture/review.json +17 -0
- package/tests/fixtures/verify_playground/product/e2e/scripts/atlas-prepare.mjs +4 -0
- package/tests/fixtures/verify_playground/product/prd/README.md +5 -0
- package/tests/fixtures/verify_playground/product/prd/home-prd.md +62 -0
- package/tests/fixtures/verify_playground/product/prd/prd.md +52 -0
- package/tests/fixtures/verify_playground/product/stack-profile.yaml.tpl +18 -0
- package/tests/fixtures/verify_playground/site/index.html +6 -0
- package/tests/test_ac_adapters.py +312 -0
- package/tests/test_adapter_api.py +253 -0
- package/tests/test_adapter_models.py +236 -0
- package/tests/test_apply_wiring.py +136 -23
- package/tests/test_atlas_check.py +371 -0
- package/tests/test_atlas_verify.py +335 -0
- package/tests/test_atlas_verify_playground.py +162 -0
- package/tests/test_canonical_consistency.py +5 -2
- package/tests/test_closeout_derive.py +264 -0
- package/tests/test_duty_bug_families.py +355 -3
- package/tests/test_e2e_console.py +49 -0
- package/tests/test_gen_e2e_scripts.py +650 -17
- package/tests/test_install_selfcheck.py +76 -0
- package/tests/test_layout.py +252 -0
- package/tests/test_map_anchors.py +475 -0
- package/tests/test_no_framework_names.py +86 -0
- package/tests/test_project_wiring.py +12 -4
- package/tests/test_refresh_facts.py +18 -3
- package/tests/test_shared_kernel.py +50 -0
- package/tests/test_tools.py +257 -1
- package/tests/test_validate_ac_alignment.py +1081 -0
- package/tests/test_validate_adr.py +24 -0
- package/tests/test_validate_walkthrough.py +313 -0
- package/tests/test_validators.py +499 -1
- package/tests/test_write_claim.py +236 -0
- package/validators/__pycache__/validate_ac_alignment.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_adr.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_design_exemptions.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_e2e_index.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_e2e_mocks.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_prd.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_stack_profile.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_structure.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_testids.cpython-312.pyc +0 -0
- package/validators/__pycache__/validate_walkthrough.cpython-312.pyc +0 -0
- package/validators/validate_ac_alignment.py +439 -183
- package/validators/validate_adr.py +15 -5
- package/validators/validate_design_exemptions.py +15 -6
- package/validators/validate_e2e_index.py +109 -38
- package/validators/validate_e2e_mocks.py +12 -5
- package/validators/validate_prd.py +109 -14
- package/validators/validate_stack_profile.py +62 -8
- package/validators/validate_structure.py +9 -2
- package/validators/validate_testids.py +36 -23
- package/validators/validate_walkthrough.py +300 -0
package/DESIGN.md
CHANGED
|
@@ -427,7 +427,7 @@ adapters:
|
|
|
427
427
|
|
|
428
428
|
默认在 atlas 各环与 `apply` 时跑;**可选**再加 git pre-commit 钩子做兜底。
|
|
429
429
|
|
|
430
|
-
**实现状态**:
|
|
430
|
+
**实现状态**:10 个校验器(`validate_{structure,stack_profile,prd,testids,e2e_index,design_exemptions,e2e_mocks,ac_alignment,adr,walkthrough}.py`)+ `scripts/atlas_check.py`(门总览:校验器 / 出厂测试 / 生成物 / 真跑,**一门一行 `ok|FAIL|WARN|SKIP`**)。
|
|
431
431
|
|
|
432
432
|
**门总账与收口清单(2026-09-28 落地,`3509 §B112`/`§B114`)**:所有门登记在 `shared/gates.md`(含**对账平面 / 显式盲区 / 验证预算 / 假绿测试取证等级**三列)—— **不在表 = 不存在**;人侧收口步骤表 = `shared/closeout.md`(此前被多处定形引用却长期不存在)。`SKIP` 是**一等结论**(未就绪),不是通过。
|
|
433
433
|
|
|
@@ -574,7 +574,7 @@ adapters:
|
|
|
574
574
|
| D105 | 生成的运行器配置**必须声明 `expect.timeout` 下限**(2026-10-17,issue #28;真跑现场驱动) | ① **病灶**:#27 落地后真跑仍 `3 failed 1 passed`,而**元素真在**(探针实测 `disabled=true` + 合规 `aria-label`)、三处导航 URL 全对 ⇒ 红因是**断言超时**:生成的 `playwright.config.ts` **没有 `expect` 段** ⇒ 继承 Playwright **默认 5s**,而该应用首屏实测 **14,184ms**(dev bundle 13.8MB)⇒「元素还没渲染」被误判成「元素不存在」= **假红**。② **定案**:生成器在两处 config 分支都发射 `expect: { timeout: Number(process.env.ATLAS_EXPECT_TIMEOUT ?? 20_000) }`(缺省 20s,仍远小于 test 超时;`ATLAS_EXPECT_TIMEOUT` 为运维覆盖口,与 `ATLAS_SLOWMO` 同族)。**它不是「固定等待」**:`expect` 是轮询式 auto-wait,本改只抬高**上限**,不引入 sleep,不破 §5.5「不得固定等待」。③ **被否**:只给守卫 `toHaveCount` 加 timeout(其余断言仍 5s,慢一点又撞);调 test 超时(那只是兜底,不解决断言级假红);固定 sleep(契约明禁,且共享靶场必飘)。④ **代价(如实)**:真失败从 5s 变 20s 才红;可在慢环境调低。⑤ **钉与变异**:`test_runner_config_declares_expect_timeout_floor`(配置含 `expect: {` + `20_000` + 覆盖口),变异 **MB5**(摘掉发射 ⇒ 红,还原逐字节一致)。 |
|
|
575
575
|
| D106 | **真跑门的执行 cwd = 生成物目录**(2026-10-17,issue #29;真跑现场驱动) | ① **病灶**:#27/#28 落地后真跑仍 `3 failed 1 passed`,而同一份产物 **`cd scripts` 直跑 8.2s 全绿** (`--grep E2E-APP-AGENT-DETAIL-001`)——差异 = **cwd**:`atlas_check` 用 `cwd=项目根` + `-c <相对路径>` 跑,而生成物里的相对路径全是 **cwd 相对**的(`storageState: ".atlas-auth/state.json"`、`outputDir: ".atlas-run"`、mock fixture)⇒ Playwright 把 storageState 解析到 `<root>/.atlas-auth/`(**并在那儿写了一个空 state**)⇒ **读不到人的会话** ⇒ 未登录态跑用例 ⇒ 断言 20s 超时**假红**。控制台(`e2e_console`)本来就是 `cd scripts` 再跑。② **定案**:本门改为 **`cwd=<scripts_dir>` + `-c playwright.config.ts`**(与执行器同一口径,两个入口不再各说各话)。③ **被否**:把生成物里的相对路径全改成绝对路径(派生件带机器绝对路径 = 不可移植、且破「生成物 = 确定性派生物」);只修 setup 的 state 路径(其余相对路径仍会漂)。④ **教训**:**「同一份产物在两个入口下跑出不同结论」必须先查 cwd / 环境**,否则会把执行器差异误判成用例或实现缺陷(本轮依次误判为「超时不足」→ 已由 D105 兜住超时面,再查出真因 = cwd)。⑤ **钉与变异**:`test_check_e2e_runs_with_scripts_dir_as_cwd`(截 `run()` 的 (cmd, cwd),断言 cwd = scripts 且 cmd 里无相对配置路径),变异 **MB6**(退回 root + 相对 `-c` ⇒ 红,还原逐字节一致)。 |
|
|
576
576
|
| D107 | 零步基线**每个探针都必须「缺席即判不成立」,不得等挂载**(2026-10-17,issue #30;真跑现场驱动) | ① **病灶**:`atlas_check --page app-agent-detail` ⇒ 两条真用例全绿,唯零步基线 `Test timeout of 30000ms exceeded`。带参页面的页级路由本就不渲染详情(`D104` 允许的形态),而 `attr` 探针发射的是 `getByTestId(t).first().getAttribute(n)` —— **无 `count()` 守卫**,Playwright 对**未挂载**元素的 `getAttribute` 会等到默认超时 ⇒ 4 探针 × 2 趟撑爆 test 预算。契约 §5.5 早已明写「基线探不到元素按 `null = 不成立` 处理」⇒ **等挂载 = 违约**(A1 判错,非项目缺陷)。② **定案**:`attr` 探针加 `count() > 0 &&` 前置(缺席 ⇒ `false` = 不成立);并把该性质上升为**生成器的通用不变式**:**任何探针表达式都必须带缺席守卫**(`count()) > 0` 或 `tsVis(` / `tsVisible(` / `atlasValueOf(`)——由通用钉守着,防未来新增动词再犯。③ **被否**:给基线设更大的 test 超时(把「等挂载」合法化,掩盖违约);带参页面跳过基线(契约 §5.5 已选择「回 null」而非「跳过」);只在 v2 侧声明 `守卫:`(那是**资产选择题**,不修 atlas 违约面)。④ **钉与变异**:`test_zero_step_probes_never_wait_for_attachment`(对**全部**探针做守卫检查)+ 变异 **MB7**(摘 `attr` 的守卫 ⇒ 红,还原逐字节一致)。⑤ **出库判定**:探针行为属**运行面** ⇒ 待下一轮真跑复验后出库。 |
|
|
577
|
-
| D108 | E2E 真跑失败**自动留证**——trace / screenshot 只在失败落盘(2026-10-18,`proposals/RFC-20261018-01-e2e-failure-evidence`;外部发版雷达首跑 5 条机制级情报中唯一采纳项,独立评审两轮 + 维护者拍板「按推荐继续」) | ① **病灶**:真跑偶发红(节拍类)现场的取证上限 = 文本尾部 80 行(`e2e_console.py:520`(采集即截)/ `:582`(docstring 失败才带尾)/ `:591-592`(落账条件)),元素几何 / 命中点 / 时序类判据只能靠 `/tmp` 临时探针(跨会话不可复算;D98 动词批与案 6 两次付费先例);`duty-review/bugs.md` 未解观察(hover 禁用按钮首跑偶发 `Element is not visible`,3 连跑全绿)的**升判条件**「复现 ≥2 次且能给出固定节拍证据(trace / 连续截图)⇒ 转 DR4」悬空在等证据源(`bugs.md:17`)。② **定案**:生成物两轨 playwright 配置补 `trace: 'retain-on-failure'` + `screenshot: 'only-on-failure'`——失败现场自动落 trace(DOM 快照 + 网络 + 操作日志,trace viewer 可逐帧回放)与截图到 Playwright 缺省 `test-results/`,**全绿轮零新增构件**;不溯及已装配副本 ⇒ **咬合点 = 下一次 `install.sh --update` / 首装重装配**,重装配前「契约已声明留证而现场无 trace」属**如实态、非缺陷**,巡检不得误报(对齐 `shared/single-source.md` §3 迁移仪式「新源生效 ⇔ 旧源显式失效」);留证含业务内容,只落采纳仓本地、不进 atlas 上报通道(§15 零业务上下文出站面不破);不解决 D103「人工会话取出 ⇒ CI 不可复现」盲区(trace 只重放**已发生**的失败,真身是登录态不是取证手段)。③ **被否**:`video` 缓行(trace 自带回放器,video 是粗粒度投影,多一键 = 双轨同构面 +2 无收益;缓行条件 = 实跑出现 trace 覆盖不了的取证需求才另起决策行);全量常录 `on`(磁盘 × 每用例,成功轮留证无消费方);留证键加环境开关(= 给「成功轮也留证」开后门);独立录制管理器 / 上传通道 / 轮转清理(运行时平台机制,Atlas 零运行时);**失败自动重试 N 次**(与「节拍类首跑不坐实、复跑复现才改判」(`bugs.md` E2E 段)方向相反,会把真缺陷静默吸收成绿——**防复读机裁决**已落 `duty-review/QUEUE.md` 归档区,外部情报再提 retry 引用可径行否决);托管云沙盒会话(违「项目自足」,同归档区裁决)。④ **钉与变异**:钉 3 条——`test_failure_evidence_both_tracks_declare_trace`(双轨同构:`mode:none` 轨与预置登录轨都必须含两键)/ `test_failure_evidence_values_are_closed`(值形态封闭:恰为 retain-on-failure / only-on-failure,`video` 混入 / `on` 常录 ⇒ 红)/ `test_failure_evidence_has_no_override_switch`(无覆盖口),`tests/test_gen_e2e_scripts.py` 74 → **77** 条;变异 **MB10**(摘 none 轨 trace/screenshot 行)/ **MB11**(值改 `on`),`mutation_proof` 四段协议双绿(基线 3 passed → 注入 rc=1 → 还原逐字节一致 → 复绿),注入 diff 摘录于 `proposals/REVIEW-2026-10-18-e2e-failure-evidence.md`;契约条 = `rings/e2e/reference.md` §7.1「失败留证(硬,失败才落盘)」,门行 = `shared/gates.md` 2.2。⑤
|
|
577
|
+
| D108 | E2E 真跑失败**自动留证**——trace / screenshot 只在失败落盘(2026-10-18,`proposals/RFC-20261018-01-e2e-failure-evidence`;外部发版雷达首跑 5 条机制级情报中唯一采纳项,独立评审两轮 + 维护者拍板「按推荐继续」) | ① **病灶**:真跑偶发红(节拍类)现场的取证上限 = 文本尾部 80 行(`e2e_console.py:520`(采集即截)/ `:582`(docstring 失败才带尾)/ `:591-592`(落账条件)),元素几何 / 命中点 / 时序类判据只能靠 `/tmp` 临时探针(跨会话不可复算;D98 动词批与案 6 两次付费先例);`duty-review/bugs.md` 未解观察(hover 禁用按钮首跑偶发 `Element is not visible`,3 连跑全绿)的**升判条件**「复现 ≥2 次且能给出固定节拍证据(trace / 连续截图)⇒ 转 DR4」悬空在等证据源(`bugs.md:17`)。② **定案**:生成物两轨 playwright 配置补 `trace: 'retain-on-failure'` + `screenshot: 'only-on-failure'`——失败现场自动落 trace(DOM 快照 + 网络 + 操作日志,trace viewer 可逐帧回放)与截图到 Playwright 缺省 `test-results/`,**全绿轮零新增构件**;不溯及已装配副本 ⇒ **咬合点 = 下一次 `install.sh --update` / 首装重装配**,重装配前「契约已声明留证而现场无 trace」属**如实态、非缺陷**,巡检不得误报(对齐 `shared/single-source.md` §3 迁移仪式「新源生效 ⇔ 旧源显式失效」);留证含业务内容,只落采纳仓本地、不进 atlas 上报通道(§15 零业务上下文出站面不破);不解决 D103「人工会话取出 ⇒ CI 不可复现」盲区(trace 只重放**已发生**的失败,真身是登录态不是取证手段)。③ **被否**:`video` 缓行(trace 自带回放器,video 是粗粒度投影,多一键 = 双轨同构面 +2 无收益;缓行条件 = 实跑出现 trace 覆盖不了的取证需求才另起决策行);全量常录 `on`(磁盘 × 每用例,成功轮留证无消费方);留证键加环境开关(= 给「成功轮也留证」开后门);独立录制管理器 / 上传通道 / 轮转清理(运行时平台机制,Atlas 零运行时);**失败自动重试 N 次**(与「节拍类首跑不坐实、复跑复现才改判」(`bugs.md` E2E 段)方向相反,会把真缺陷静默吸收成绿——**防复读机裁决**已落 `duty-review/QUEUE.md` 归档区,外部情报再提 retry 引用可径行否决);托管云沙盒会话(违「项目自足」,同归档区裁决)。④ **钉与变异**:钉 3 条——`test_failure_evidence_both_tracks_declare_trace`(双轨同构:`mode:none` 轨与预置登录轨都必须含两键)/ `test_failure_evidence_values_are_closed`(值形态封闭:恰为 retain-on-failure / only-on-failure,`video` 混入 / `on` 常录 ⇒ 红)/ `test_failure_evidence_has_no_override_switch`(无覆盖口),`tests/test_gen_e2e_scripts.py` 74 → **77** 条;变异 **MB10**(摘 none 轨 trace/screenshot 行)/ **MB11**(值改 `on`),`mutation_proof` 四段协议双绿(基线 3 passed → 注入 rc=1 → 还原逐字节一致 → 复绿),注入 diff 摘录于 `proposals/REVIEW-2026-10-18-e2e-failure-evidence.md`;契约条 = `rings/e2e/reference.md` §7.1「失败留证(硬,失败才落盘)」,门行 = `shared/gates.md` 2.2。⑤ **出库判定(2026-10-25 真跑日出库,D148 首轮)**:红轮 28 失败构件(trace.zip + 截图成对)/ 绿轮零构件,两面实证(咬合记录 = `shared/gates.md` 2.2「D108 首次咬合」);**落点勘误随批**(实际 `.atlas-run/`,非首版误写的缺省 `test-results/`)+ **留存窗口观察在案**(Playwright 起动清空 outputDir ⇒ 留证只存到下一次起跑,`duty-review/bugs.md` E2E 段带升格条件)。 |
|
|
578
578
|
| D109 | **逐用例宿主注入 URL 参数通道**:`goto` 查询门**重靶** + 项目级白名单(2026-10-18;字段与门落在 `rings/e2e/reference.md` §5.5 / §8.2;采用方现场驱动) | ① **病灶**:`会话:`(D89)解决的是 **sessionStorage** 那一个消费面;而某类应用的被测参数**只从载入地址的常规查询串读**(`window.location.search`)⇒ **无合法写法**(`goto` 带 `?` 是硬 FAIL、`auth` 只管登录态、`seed:` 绑数据实体)—— D104 ① 已把这条记为「三条各自成立 ⇒ 无合法写法」,当日改选 B′(base query 全局转发)并把「`会话:` 扩 URL 参数档」列入被否清单。采用方现场(2026-10-18):两条用例被迫写成**同一前提**(都走 sessionStorage)⇒ 重复,而查询串那一面**零覆盖**。② **重靶取证(关键,本行的依据)**:原门(§5.7,2026-09-23;事故 `3509 §B20` = `E2E-LOGIN-003` 断言 `?state=forbidden` 演示横幅**自身文案**)的**靶子随 E1 原型环退役而消失** —— `?state=` 只出现在本文件 `:460` 的**已作废**决策行(D8 两段式 / D56 原型交互分级);E1 后**无任何决策行**重新论证该门;`duty-review/bugs.md` 与 `CHANGELOG.md` **零起**该门事故;今日它在 D104 ① 里的身份是「无合法写法」的**阻碍** = 制造成本,而非拦事故。该门今日真正剩下的(`reference.md` 旧 §5.7 邻域的那条)是**另一族、不限于原型**的纪律:「前置数据态只能用真实界面动作达到」—— **而这族边界在重靶前后都由项目级白名单守**,与「`goto` 带不带 `?`」无关 ⇒ 取舍只剩成本与表达力。③ **定案**:(a)门**重靶** `goto 不带查询参数` → **`goto 查询键须已声明`**(键须 ① ∈ `e2e.query_preset.keys` ∧ ② ∈ 本用例 `参数:`;**原靶仍在 FAIL 面内**:未声明键照样红);(b)用例块新增可选 `参数:`(**只声明键**,值内联在 `step` 的 `goto` 查询串里 = 单一来源),**守卫导航取该用例首个带参 `goto` 的查询串**(否则守卫读的是**无前提的 DOM**:参数决定「哪些元素在场」的页面上守卫会在 `goto` 后即红);(c)`_support/url_merge.merge_base_query` 的「目标自带 `?` ⇒ 原样返回」**暗位转正为并集档**(base 在前、**同键 ⇒ 用例胜**)——该分支原注释即写着「§5.7 下不可达,**为未来扩展留位**」,且既有生成物无一带 query 的 `goto` ⇒ **零回归**;(d)三道新门(声明有效 / 值形态 / 同键双载体)+ 生成期同源中止(双保险)。④ **被否**:(A)「值只写 `参数:` 行、`goto` 保持无 query」—— 其立论之一是「保住 §5.7 事故防护」,取证后该论据**作废**(见 ②),取舍只剩成本与表达力,B 胜;其余照 D104 ⑤(运行期 TS 助手 / 把「同名键以目标为准」写成通则)。⑤ **边界(如实登记,不得 oversell)**:允许带白名单查询串后,「数据态直达」那族纪律的**机器面只剩白名单本身**(「项目说合法就合法」)—— 与 `会话:` 同族、是本设计的本来形态;登记以防后人以为还有别的网在兜。⑥ **钉与变异**:`tests/test_validators.py` **116 → 123**(重靶钉改名 1 + `参数:` 门 6);`tests/test_gen_e2e_scripts.py` **+6**(两处导航面承载 / 基线不施加 / 无白名单中止 / 白名单外中止 / 死声明中止 / 值形态中止);`_support/url_merge.py` 并集档(原「原样保留」单元钉随之改判)。变异 **MB12**(并集档退回「原样返回」⇒ 单元钉 + 守卫承载钉齐红)、**MB13**(守卫不追加用例参数 ⇒ 守卫面钉红),两枚均四段协议通过、还原逐字节一致,注入 diff 见任务件 `.trellis/tasks/10-18-atlas-url-param-channel/check-report.md`。设计依据 = 采用方现场(`src/entry/main/app.tsx:18/:36` 两个消费面 + `validators/validate_e2e_index.py` 旧门 + `_support/url_merge.py:48`)。 |
|
|
579
579
|
| D110 | 控制台**逐用例数据准备钩子**:项目自带 `product/e2e/scripts/atlas-prepare.mjs` ⇒ 执行前必跑、失败即拒(2026-10-18,v3 现场实证移植批;病灶账 = `duty-review/bugs.md` 005/006 链 companion;契约条 = `rings/e2e/reference.md` §10「执行」行 / `e2e_console` docstring) | ① **病灶**:共享状态型靶场(SPA + 服务端态)的用例 `precondition` 属**文档性声明**、运行器不自动归零 ⇒ 残留实体使 count 类断言系统性偏移(现场实测 Expected 1 · Received 5);且控制台此前对「执行前重置」零消费——`e2e.reset`(`shared/stack-profile.md` §2)只被 `atlas_check` 真跑门执行(**批级**:整轮一次),逐条执行面无 per-case 前提通道。② **定案**:执行器在每条用例**计时起点之前**跑该脚本(`node <e2e.scripts_dir>/atlas-prepare.mjs`,cwd = 项目根,180s 上限,**不占**用例超时窗);失败(非零)/ 超时 / 起不动 ⇒ **拒绝执行该条**:`done` 事件按完整形状写入(`ok=False` / `exit` 如实 / `prep_failed=True`),失败原文进「输出」面板——不静默跑注定偏移的用例(对齐执行器「解析不成立 ⇒ 诚实拒绝」同族口径)。③ **与 `e2e.reset` 的关系(登记,防后人重复起案)**:**不并轨**——`reset` 是批级且值形态为 shell 命令串(项目在 profile 声明)、本钩子是逐条且文件约定(项目在 `<e2e.scripts_dir>` 放脚本),现场 `reset: null`、两粒度各占一格;控制台若要消费 `reset` 须另起决策行。④ **被否**:由生成脚本内自查归零(把环境初值达序藏进生成物 = 静默吸收);自动执行 precondition 声明(人读文档,无机器可执行形态);prepare 失败仍照跑(偏离移用例 = 假绿通道)。⑤ **钉与变异**:`tests/test_e2e_console.py` **13 → 14**(钩子先于真跑 / 失败拒绝并带 `prep_failed` / 起不动 `exit=-2` 三型一钉);变异 **M94**(摘 prepare 钩子段 ⇒ 钉红,还原逐字节一致)。设计依据 = v3 装配副本手改(现场实证可用);主仓移植时审校掉装配副本失败路径的 `self._lines` 属性错拼(会 AttributeError 让执行线程挂死、`running` 永真),统一改走 `self._emit`(持锁 + notify)。 |
|
|
580
580
|
| D111 | `refresh` 落盘节拍参数化:发射 `ATLAS_REFRESH_SETTLE` 注入的 `waitForTimeout`(先等后刷),默认 0 行为不变(2026-10-18,v3 现场实证移植批;病灶账 = `duty-review/bugs.md` 005 链;契约条 = `rings/e2e/reference.md` §7.1 / §5.5 `refresh` 行) | ① **病灶**:`refresh` 裸 `reload` 对启用**节流型自动保存**的项目(距上次成功保存 < 节流窗则延迟落库)必丢步骤编辑内容(现场实测 Expected 1 · Received 0——刷新发生在保存落库之前)。② **定案**:发射**先等后刷**两行——`await page.waitForTimeout(Number(process.env.ATLAS_REFRESH_SETTLE || "0"));` + `await page.reload();`;**默认 0 ⇒ 生成物行为与既有契约一致**。通道选择同 F11:Playwright `test` CLI 无逐用例注入面,env 是唯一通道。③ **零业务常数(硬)**:毫秒数**不进生成器**——它属项目侧**运行时**前提,现场以 `ATLAS_REFRESH_SETTLE=<毫秒>` 于启动面注入(对齐 `ATLAS_SLOWMO` / `ATLAS_EXPECT_TIMEOUT` 同族);它也不是 §5.5「固定 sleep」禁令所指(该禁令管**用例正文**不把共享靶场节拍写死;本行是执行环境配置面)。④ **被否**:硬编码任何项目常数进源包(§15 零业务上下文纪律);生成期读项目配置算节拍(生成物 = 确定性派生物,节拍是运行时动态值)。⑤ **钉与变异**:`tests/test_gen_e2e_scripts.py` **83 → 84**(先等后刷顺序钉);变异 **M95**(摘 `waitForTimeout` 发射行 ⇒ 钉红,还原逐字节一致)。 |
|
|
@@ -596,13 +596,56 @@ adapters:
|
|
|
596
596
|
| D127 | 控制台「已提交」视图按**事件流累积合并**(2026-10-21;`scripts/e2e_console.py::Ledger.committed_snapshot`;契约 §10「执行」) | ① **病灶**:单次提交只写页面草稿里本次执行/勾选的若干条用例,而 `cases_payload` 的 `committed` 取 `last_submit`(最后一条 submit 事件)⇒ 后一次只提交 1 条时,之前提交过的用例从 committed 视图整体消失;提交会清空浏览器草稿,于是控制台把已执行/已评审的用例重新显示为「未跑」(现场:账本最后一条只含 013,010/012 的历史提交被抹掉)。② **定案**:新增 `Ledger.committed_snapshot(source)`,按 append-only 事件顺序合并所有 submit 的 `cases`(后提交覆盖同名用例),`ts/actor` 取最后一条;`cases_payload` 与 `_submit` 改用该视图;`last_submit` 保留「最近事件」语义供时间戳/诊断使用。③ **被否**:提交时把全量累积快照写进每条事件(事件膨胀、重复存储,与「草稿=差量」语义冲突);提交后保留草稿(重复计数/重复提交风险);UI 侧按事件回放(把协议语义塞进展示层,其他消费者仍会踩同一个坑)。④ **钉与变异**:`test_committed_snapshot_merges_partial_submits`(部分提交合并 / 同名覆盖 / source 隔离 / legacy list 形态跳过);变异 **M128**:把 `committed_snapshot` 退回 `last_submit` ⇒ 钉红,还原逐字节一致。 |
|
|
597
597
|
| D128 | 控制台人工评审绑定**提交与脚本指纹**,引入陈旧度(stale)判定与 UI 拦截(2026-10-21;`scripts/e2e_console.py`、`templates/e2e/console/app.js`;契约 §10.1) | ① **病灶**:控制台账本此前仅记录 `reviewed.pass` 与执行快照,**无任何代码提交(HEAD SHA)或测试脚本指纹**。人审完某条用例后,若代码或生成脚本发生改动,账本仍显示「已评审」,前端仍渲染勾选态,造成「曾经有人审过 = 当前版本已验收」的假象(审查逃逸)。② **定案**:① 提交事件与逐用例快照追加 `prov: {head_sha, scripts_hash, git_dirty, actor_type}`;`scripts_hash` 白名单覆盖 `product/e2e/scripts/` 源码及 `atlas-prepare.mjs`,严格排除运行时产物(`test-results/`、trace、png 等);② `committed_snapshot` 按当前工作区指纹自动计算 `review_state` 四态闭集(`fresh` / `stale` / `legacy` / `unreviewed`);若 HEAD 或脚本变动,标为 `stale`;③ 前端 UI:`isReviewed` 将 `stale` 剔除出「已评审」;用例行渲染 `STALE 需重审` 徽标、禁用通过复选框,横幅提示变动原因;顶栏统计增加 `需重审` 项;重新执行该用例自动清空旧评审态;④ 历史旧账本缺失指纹标记为 `legacy`,维持兼容但不冒充 fresh。③ **被否**:每次执行(done事件)均重新跑 git subprocess(导致运行期开销与单元测试 mock 冲突);把 stale 做成阻断性硬门禁(E2E 控制台是辅助工作台,评审阻断由收口门统一把控)。④ **钉与变异**:`tests/test_e2e_console.py` +4(`test_scripts_hash_excludes_runtime_artifacts_and_whitelists_scripts` / `test_case_review_state_lifecycle` / `test_submit_attaches_provenance_and_committed_computes_stale` / `test_console_app_js_handles_stale_and_legacy_and_provenance`);变异 **M129**(摘掉 stale 判定退回恒 fresh ⇒ 钉红),还原逐字节一致。全量测试通过。 |
|
|
598
598
|
| D129 | 独立审查报告契约扩展为 **`schema_version: 2`**,引入缺陷指纹去重、存量代码标记、处置留痕与轮次上限(2026-10-21;`shared/independent-review.md` §3/§4;`_support/review_policy.py`、`scripts/validate_review_report.py`、`scripts/cli_review_convert.py`、`scripts/gen_e2e_scripts.py`) | ① **病灶**:① 原契约状态映射硬死(有任一 Critical 即 FAIL),**无法区分改动引入的新缺陷与存量旧代码问题**;Agent 评审实测 59% 为既有存量代码,导致微小改动因存量问题被无限阻断;② 缺乏 finding 去重指纹,导致相同问题跨轮重复刷分;③ 缺乏轮次上限,实测出现连夜 50+ 轮死循环;④ 缺乏显式处置闭环(disposition),误报(false positive)无法由人或架构签字排除。② **定案**:① 设立单源内核 `_support/review_policy.py`;② `review.json` 引入 `schema_version: 2`;`checks[]` 扩充 `fingerprint`(排除行号的稳定哈希)、`pre_existing`(bool)、`disposition`(open/fixed/false_positive/intentional/ignored)、`verification`;③ 状态映射:阻断性 Critical = `Critical ∧ pre_existing!=true ∧ disposition未关闭`;`pre_existing=true` 的 Critical 降级为 WARN 下限(记录存量,不静默放行);带签字与取证的 `false_positive` 降级为 WARN;重复 fingerprint ⇒ 校验器 FAIL;④ 轮次收敛:round >= 4 必须提供 `--escalate-to` 与 `--escalate-reason` 升级复议,否则拒绝放行;⑤ 首轮后支持 `--delta-only` 差量复审;⑥ 缺省 `schema_version: 1` 维持既有全阻断逻辑,存量历史报告 100% 兼容。③ **被否**:把 disposition 做成无需签字的口头开关(开严重安全后门);由模型自审关闭 Critical(违背独立审查原则,必须人/架构签字)。④ **钉与变异**:`tests/test_review_report.py` +4、`tests/test_cli_review_convert.py` +3(含 `test_fingerprint_stable_across_line_numbers`:行号漂移后指纹必须不变、内容不同指纹必须不同——评审抓到 CLI 恒产 `path:12-15` 而旧正则剥不掉 ⇒ 去重门永不命中)、`tests/test_gen_e2e_scripts.py` +1;变异 **M130**(摘掉 pre_existing 豁免退回原写死 FAIL ⇒ 钉红)、**M130b**(摘 `validate_review_report` 的 fingerprint 去重段 ⇒ 重复指纹钉红)、**M130c**(`clean_target_file` 行号正则退回 `:\d+$`,剥不掉 CLI 恒产的 `path:12-15` ⇒ 指纹稳定性钉红),四段协议(基线绿→注入红→逐字节还原→复绿)全部通过、还原 sha256 逐字节一致。全量测试通过。 |
|
|
599
|
-
| D130 | E2E 环引入 **前端适配版 C01-C14 覆盖维度矩阵** 与缺口对账(2026-10-21;`rings/e2e/reference.md` §5.1/§5.2/§5.5/§8.2;`_support/case_block.py`、`validators/validate_e2e_index.py`、`templates/prd/domain.md`、`templates/e2e/case-shard.md`) | ① **病灶**:原「AC 反向覆盖门」仅能回答「每条 AC 是否至少被一条用例指向」,**无法回答每条 AC 的异常流、边界值、权限、会话恢复、幂等、状态转换等高危场景是否覆盖**;导致 Agent 生成大量表面正向用例,真正易崩场景系统性遗漏。② **定案**:① 前端适配裁剪版 C01-C14 场景维度闭集(C01正常流、C02异常流、C03边界值、C04权限、C05并发、C06重试、C07审计、C08会话恢复、C09一致性、C10依赖容错、C11幂等、C12输入校验、C13状态转换、C14安全);② 用例分片块体增加可选数组字段 `覆盖维度:`,单源解析器 `_support/case_block.py` 纳入键空间;③ 域级 PRD 验收标准表增加可选第 4 列 `适用覆盖维度`(如 `C01, C02`),前三列保持不变;④ 校验器 `validators/validate_e2e_index.py` 增加「AC × 覆盖维度」对账项:已声明但未全量覆盖 ⇒ WARN;未知维度词 ⇒ WARN;未声明第 4 列的项目 ⇒ N/A 兜底,绝不产生破坏存量的永久红门;⑤ 控制台 snapshot case 对象透传 `dimensions` 字段。③ **被否**:在 PRD 新建第 4 张手工对账表(严重违反 single-source 契约,且会与 AC 唯一性定义门发生行号碰撞);对所有 AC 强制 14 维度全覆盖(引发无脑生成几十条形式用例的膨胀灾难)。④ **钉与变异**:`tests/test_validators.py` 增加 `test_e2e_index_coverage_dimensions_pass_and_warn`(N/A / WARN / PASS 三态验证);变异 **M131**(摘掉 dimensions 聚合回落全 PASS ⇒
|
|
599
|
+
| D130 | E2E 环引入 **前端适配版 C01-C14 覆盖维度矩阵** 与缺口对账(2026-10-21;`rings/e2e/reference.md` §5.1/§5.2/§5.5/§8.2;`_support/case_block.py`、`validators/validate_e2e_index.py`、`templates/prd/domain.md`、`templates/e2e/case-shard.md`) | ① **病灶**:原「AC 反向覆盖门」仅能回答「每条 AC 是否至少被一条用例指向」,**无法回答每条 AC 的异常流、边界值、权限、会话恢复、幂等、状态转换等高危场景是否覆盖**;导致 Agent 生成大量表面正向用例,真正易崩场景系统性遗漏。② **定案**:① 前端适配裁剪版 C01-C14 场景维度闭集(C01正常流、C02异常流、C03边界值、C04权限、C05并发、C06重试、C07审计、C08会话恢复、C09一致性、C10依赖容错、C11幂等、C12输入校验、C13状态转换、C14安全);② 用例分片块体增加可选数组字段 `覆盖维度:`,单源解析器 `_support/case_block.py` 纳入键空间;③ 域级 PRD 验收标准表增加可选第 4 列 `适用覆盖维度`(如 `C01, C02`),前三列保持不变;④ 校验器 `validators/validate_e2e_index.py` 增加「AC × 覆盖维度」对账项:已声明但未全量覆盖 ⇒ WARN;未知维度词 ⇒ WARN;未声明第 4 列的项目 ⇒ N/A 兜底,绝不产生破坏存量的永久红门;⑤ 控制台 snapshot case 对象透传 `dimensions` 字段。③ **被否**:在 PRD 新建第 4 张手工对账表(严重违反 single-source 契约,且会与 AC 唯一性定义门发生行号碰撞);对所有 AC 强制 14 维度全覆盖(引发无脑生成几十条形式用例的膨胀灾难)。④ **钉与变异**:`tests/test_validators.py` 增加 `test_e2e_index_coverage_dimensions_pass_and_warn`(N/A / WARN / PASS 三态验证);变异 **M131**(摘掉 dimensions 聚合回落全 PASS ⇒ 钉红),还原逐字节一致。全量测试通过。**(2026-10-26 D153:第 4 列明确为 E2E 通道;后端列 `后端适用覆盖维度` 另立,不改变本条判据。)** |
|
|
600
600
|
| D131 | 收口契约引入 **变更风险定档、Always-Human 路径、修复验证 7 检查点与事故追问闭环**(2026-10-21;`shared/closeout.md` §2 步骤 3/4/11;`duty-review/README.md` §2;`templates/reviews/README.md`) | ① **病灶**:① 收口阶段缺乏系统性风险定档,高危底层改动(如权限、路由、CI、包依赖)容易被 Agent 自述误导而跳过严格人工亲测;② 缺陷修复任务(bug fix)常直接修改业务代码并自报已修复,缺乏「先失败后转绿」的 Red First 证明与边界/回归核对,导致同类问题反复逃逸;③ 线上/联调事故复盘停留在修代码,未追问「哪一道门本该接住」,事故没有转化为常态化回归用例或变异钉。② **定案**:① `shared/closeout.md` 补齐步骤 3「变更风险定档与人工审批」:三档风险矩阵,明确 `auth / permission / routing / CI / package deps / shared contracts` 为 Always-Human 永远人工路径,严禁 Agent 代勾放行;② 补齐步骤 4「缺陷修复验证 7 检查点」(复现明确、Red First 先失败测试、最小 diff、边界值、状态机完整、回归范围界定、闭环证据留存);③ 步骤 11 与 `duty-review/README.md` 引入事故追问机制:每个确认逃逸缺陷必须在 `bugs.md` 标注原因,并在关闭前补齐 E2E 回归用例或 M 系列变异钉;④ 提供 `templates/reviews/README.md` 代码审查指引轻量模板(≤10 行,聚焦失败特征与证据链)。③ **被否**:用文件名/正则做脆弱的机器强制路径阻断脚本(极易被重命名或参数绕过,且易对合法改动造成假阻断);由 Agent 自动签署审批(违背人类作为最终交付负责人的第一原则)。④ **钉与变异**:`test_canonical_consistency.py` 计数与跨文件一致性全量跑绿。 |
|
|
601
601
|
| D132 | 新增 **E2E hash 查询参数契约**(`hash参数:` + `e2e.query_preset.hash_keys`)(2026-10-22;`rings/e2e/reference.md` §5.5/§5.7/§8.2;`shared/stack-profile.md` §2;`_support/case_block.py` / `_support/url_merge.py` / `scripts/gen_e2e_scripts.py` / `validators/validate_e2e_index.py`) | ① **病灶**:HashRouter 页面的 `useSearchParams` 只读 `#` 之后的查询段;D109 `参数:` 只覆盖 `#` 之前的常规查询,`会话:` 是 sessionStorage,导致快照页这类「`#` 后查询前提」用例无合法写法,只能手补生成物(重跑即丢)。② **定案**:`hash参数:` 只声明键,值内联在 `goto` 的 hash 查询串;白名单 `query_preset.hash_keys`;守卫导航把同一段 hash 查询追加到 hash 段(`append_hash_query`),base query 仍在 `#` 前;零步基线不施加;路径门两侧 `strip_queries` 后比对;同键不得跨 `参数:`/`会话:`/`hash参数:` 双写。③ **被否**:把 `#` 后查询硬塞进 `参数:`(会丢 hash 段 / 与 base query 合并语义冲突);项目侧手补生成物(不可重生成,属已否的 D98 类补丁)。④ **钉**:生成器 6 条 + 校验器 7 条(含 `test_hash_param_carried_to_guard_and_goto`、`test_zero_step_baseline_carries_no_hash_param`、`test_hash_query_merge_helpers` 与正向/未白名单/白名单外/死声明/值形态/未声明 hash 键/双载体);`tests/test_validators.py` 计数 138 已登记 `shared/gates.md`。 |
|
|
602
|
+
| D133 | `value` 断言/探针改读**可见文本**,排除触发器内的视觉隐藏 a11y 镜像(2026-10-23;`scripts/gen_e2e_scripts.py` 发射点;契约 §5.5 `value` 行) | ① **病灶**:`value` 对非原生控件用 `innerText` 读触发器文本。Ant Design Select 在用户交互后向触发器内注入 `aria-live="polite"` 的无障碍播报 span(`width:0;height:0;opacity:0`),其文本与选中项相同;`innerText` 不排除视觉隐藏节点 ⇒ 选中文案被读两遍(现场:`模式一…\n模式一…`),同一标签的 `value` 断言在交互后必红,而交互前同断言正常(基线 green / 动作后 red 的假红)。契约本体写的是「触发器**可见**文本」,实现与契约不符。② **定案**:新增发射期助手 `atlasVisibleText(loc)`:采值前遍历触发器子树,用 `checkVisibility({opacityProperty:true,visibilityProperty:true})`(缺 API 时退 `getComputedStyle`)判定,把不可见节点的 `style` 暂存后置 `display:none !important`,读 `innerText` 后**按逆序逐字节恢复 style**(`try/finally` 覆盖异常路径),保留 `innerText` 的排版归一半径;`atlasValueOf`(探针,含三态语义)与 `atlasExpectValue`(正式断言,改用 `expect.poll` 保留轮询)统一走该助手;原生 `input/select/textarea` 的 `toHaveValue` 路径不变。③ **被否**:按 `aria-live` / `aria-hidden` 选择器硬排除(只治 Ant Design 一处,其他组件库的视觉隐藏节点仍会污染;`aria-hidden` 还可能承载可见文本);读 `textContent`(会把 `display:none` 的隐藏下拉项也算进来,扩大假绿面);对读数做去重(同一文案合法重复出现时无法区分,属语义猜测)。④ **钉与变异**:`tests/test_gen_e2e_scripts.py::test_value_reads_visible_text_excluding_a11y_mirror` —— 从生成物抽出助手源码,在 Node 里以假 DOM **真求值**:可见项 + `opacity:0` 的同文案播报项 ⇒ 只返回一次;采值后隐藏节点 `style` 恢复为 `null`;同时钉发射面含 `atlasVisibleText`、不再出现 `toHaveText(v, { useInnerText: true })`。 |
|
|
603
|
+
| D136 | `after:<N>` 步骤锚按 **step** 计数,生成器内部 action 槽位必须与 step 一一对应(2026-10-23;`scripts/gen_e2e_scripts.py` 组装段;契约 §5.5 步骤锚行) | ① **病灶**:`after:<N>` 的语义是「第 N 条 step 之后」(契约 §5.5),生成器按 `actions` 列表下标挂锚,却假定一个 step 产出一个 action 元素。`refresh` 为「先等后刷」发射了**两个** action 元素 ⇒ 从 refresh 起每个后续 step 的锚点整体前移一格:现场用例 10 条 step,`after:9`(刷新后重新打开面板)实际落在「点击节点」之前,面板未刷新打开时断言必红;`after:10` 落在第 9 步之后,断言对象错位。用例越靠后错位越大,且只有「锚点落在动作语义分界上」时才暴露。② **定案**:`refresh` 的两行合并为一个 action 元素(内部换行 + 自带缩进),保持 `actions` 与 `step` 严格 1:1;锚仍按 action/step 序号对位。生成物逐行内容不变(仅组装槽位变化)。③ **被否**:把锚改成按「非 refresh 步」计数(破坏契约 N = step 序号的语义);在锚点前动态重算 step 边界(依赖动作文本,脆弱且无法覆盖未来多行动作);把 `refresh` 回退成单行(丢失 `ATLAS_REFRESH_SETTLE` 的落库等待窗,D111 已否决)。④ **钉**:`tests/test_gen_e2e_scripts.py::test_after_anchor_stays_aligned_when_step_emits_multiple_lines` —— 构造 `refresh` + `click` 两步,断言生成顺序为 `reload → after:1 → click → after:2`;修复前 `after:1` 落到 reload 之前即红。**(编号说明:D134 已被 `type` 动词占用、D135 为 testid 尾逗号,本决策顺延为 D136。)** |
|
|
604
|
+
| D134 | 新增 E2E **`type` 逐字输入动词**(2026-10-23;契约 §5.5;`scripts/gen_e2e_scripts.py` / `validators/validate_e2e_index.py`) | ① **病灶**:Playwright `fill` 一次性写 DOM、只触发 `input`,不触发带掩码 / 受控输入组件的内部解析(现场:Ant Design RangePicker 日期输入,文本可见但保存 payload 无 `startTime/endTime`);逐条 `press` 每次先 focus、只发单键,无法形成连续输入链(实测同样不提交)⇒ 项目侧只能留人工或用 `_support` 钩子代输入(步骤不可见、绕过 DSL)。② **定案**:`type <tid> <text>` → `locator.pressSequentially(text)`,逐字触发 `keydown/keypress/input`,**不先清空**原值(替换语义用 `fill`);值必填,空值 ⇒ 生成器报错中止;动作词表 13 → 14,校验器词表同步,样例守恒表 +1。③ **被否**:用 N 条 `press` 拼输入(不可读且实测不提交);`_support` 钩子代输入(步骤不可见、绕过 case DSL);移除产品侧掩码(UX 变更、超出 E2E 能力面)。④ **钉**:`test_type_verb_emits_press_sequentially_without_clearing`(发射形态 + 空值中止 + `[N]` 定位)与 `test_e2e_index_type_is_action_not_assertion`(step 侧放行 / expected 侧拒收);变异 **M134**(摘生成器 `type` 分支 ⇒ 钉红)。 |
|
|
605
|
+
| D135 | testid 提取器容忍 **`setAttribute` 尾逗号**(2026-10-23;`validators/validate_testids.py`;F22 扩展) | ① **病灶**:Prettier `trailingComma: all` 会把多行 `el.setAttribute("data-testid", "x")` 写成尾逗号形态(`"x",\n)`),旧正则要求 `"x"` 后紧邻 `)`,导致已实现 testid 被判缺失、双向集合 FAIL(现场:邮件面板 RangePicker 日期输入的多行 setAttribute)。② **定案**:`SETATTR_TESTID` / `SETATTR_TESTID_TPL` 在值后允许可选 `,`;与静态属性同档,不改变 counts / 页面归属。③ **被否**:项目侧改成单行调用(lint-staged 的 Prettier 仍会加回尾逗号);把 testid 挂到 RangePicker 容器(`fill` / `value` 指不到内层 input)。④ **钉**:`test_f22_imperative_testid_extraction_and_counts` 增加多行 + 尾逗号样本(Red First:修前 `notes-avatar` 缺失;修后抽取成功);变异 **M135**(去掉 `,?` ⇒ 钉红)。 |
|
|
606
|
+
| D137 | `atlas_check` 增加**逐门耗时**与**运行事件行**(2026-10-23;`scripts/atlas_check.py`、`tests/test_atlas_check.py`、`shared/gates.md`;P2 观测面,不参与判定) | ① **病灶**:消融首跑(`research/2026-10-23-ablation-first-run.md`)发现仓内只有失败日志与 E2E 汇总,没有逐门耗时、运行历史与「门是否真跑过」的记录 ⇒ A2「未触发」/ A4「被绕过」/ A5「预算晚」不可判,成本面 C1/C6 全空。② **定案**:`Row.add` 增可选 `duration_ms`(默认 `None`,不参与任何判据);校验器逐器计时,出厂测试 / 生成物 / 审查报告 / 串行 / 漂移 / 装配版本 / E2E 真跑按执行段计时;每轮结束向 `.atlas/runs/check-events.jsonl` **追加**一行 `{timestamp, git_head, git_dirty, ok, fast, page, ablation_id, rows:[{name,status,duration_ms}]}`;写失败只吞掉(`except Exception` + `ensure_ascii=True`,序列化/编码异常不得把检查判红);`_git_state` 的 `git_dirty` 排除 `.atlas/`(运行态产物),git 不可用为 null;`duration_ms=None` = 未计量或无可计时段(E2E 计整门 wall time,含 reset/probe);JSON 输出同步带 `duration_ms`(additive)。③ **被否**:把事件行做成硬门(观测面坏了反把检查判红);把 token 写进 Atlas(宿主无关前提,仓内拿不到);只加 E2E 耗时(静态门仍无账);改任何判据分支(本批零行为变化)。④ **钉与变异**:`tests/test_atlas_check.py` 31 → **36 条**(失败轮含耗时且追加不覆盖 / 全绿轮也写 / `runs` 被文件占用时检查仍 ok / 序列化失败不抛 / `git_dirty` 排除 `.atlas`);变异 **M136** 四段实测:基线 36 passed → 摘掉 `_write_check_event` 调用 ⇒ 2 failed / 34 passed(`test_check_events_log_records_durations_on_fail` / `test_check_events_log_written_on_ok_run`)→ 还原 sha256 一致 → 36 passed;注入 diff 见 `research/2026-10-23-ablation-p2-mutation.md`;`shared/gates.md` 的 `atlas_check` 行与 `tests/test_atlas_check.py` 计数同步。**编号说明:D136 已被 `after:<N>` 步骤锚占用,本决策顺延为 D137。** |
|
|
607
|
+
| D138 | 走查单确认门**部分机器化**:新增 `validators/validate_walkthrough.py`(M1,`RFC-20261022-01` 批 1)(2026-10-23;`shared/gates.md` §2.1/§2.5;`apply/reference.md` §3.1;`scripts/atlas_check.py`) | ① **病灶**:走查单确认门在门册自记 `[语义]` 且不设机器判据,而「走查节首有无 `> 已确认:<日期>` 行」是路由形(0/1 可判);事故本体 = 走查断链(`RFC-20261015-01`),两道同名走查门靠人交叉指认。② **定案**:时空域 = 只扫 `task.json.status ∈ {planning, in_progress}`(已归档不扫);第 2 问=「是」⇒ 报告引用的 `product/prd/<domain>-prd.md` 走查节须有 `> 已确认:YYYY-MM-DD` 行(缺失 / 形态非法 / PRD 不存在 ⇒ FAIL 指名域与文件);三问小节在但第 2 问不可解析 ⇒ WARN(不降 SKIP,防写歪措辞静默旁路);无 apply 记录 / 第 2 问否 / 无三问小节 / task 记录损坏 ⇒ SKIP 带原因;本器纳入 `VALIDATORS`。③ **被否**:让校验器脱离 apply 记录自判三问语义(重蹈 regex 判句意,即**三问语义机器判**);对已归档任务也扫(老记录挂永久红账);确认行**自动补写**(把凭据变成自证,锁失去意义)。④ **钉与变异**:`tests/test_validate_walkthrough.py` **19 条**(评审后 +3:未知 status 可见 SKIP / 第 2 问多行冲突 WARN / 一致多行取值);M138(摘确认行存在性判据)/ M139(不可解析分支降 SKIP)/ M140(摘时空域过滤)四段协议,报告含注入 diff、还原逐字节一致。 |
|
|
608
|
+
| D139 | 收口触发表**派生器**:新增 `scripts/closeout_derive.py`(M2,`RFC-20261022-01` 批 2)(2026-10-23;`shared/closeout.md` §1/步骤 1;`shared/gates.md` §2.1;`install.sh`) | ① **病灶**:`closeout.md` §1 是一张路由表(事件 → 必跑步骤),执行靠模型收口时「记得查表」;这份清单本身曾长期不存在(canonical 全仓 grep = 0 命中),说明人防成本支付不起。② **定案**:派生器只解析表、不复制表;`--diff-base` 取 `git diff` ∪ 未跟踪文件(`.gitignore` 除外);输出 stdout(不落派生文件面);触发表引用空号 / 表结构解析失败 ⇒ FAIL 响亮;告警宇宙收敛到 atlas 自有对账面,业务面只计条数;`closeout.md` 步骤 1 写「收口第一步 = closeout_derive(旧装配无此命令 ⇒ 查表照旧)」。③ **被否**:把派生结果落成**派生步骤文件**(第二真相源,会与表漂移);对业务面文件也**业务面逐条告警**(狼来了,把真信号训练成噪声,同 gates「E2E 真跑」2026-10-04 先例);让派生器替模型执行步骤(驱动权仍在模型)。④ **钉与变异**:`tests/test_closeout_derive.py` **14 条**(含非 git 短句拒绝);M141(摘告警宇宙收敛)/ M142(摘空号校验)四段协议,报告含注入 diff、还原逐字节一致;真收口对本仓 `--diff-base HEAD` 实测输出 1/2/3/5/7、零未命中告警。 |
|
|
609
|
+
| D140 | one-writer-per-file **写面 claim 机器面**:新增 `scripts/write_claim.py`(M3,`RFC-20261022-01` 批 3)(2026-10-23;`apply/reference.md` §5.1;`shared/gates.md` §2.2;`scripts/atlas_check.py`;`install.sh`) | ① **病灶**:`apply/reference.md` §5.1 的写面仲裁纯纪律(「冲突时停下报告,不得抢占」实测第三次才守住),`validate_e2e_index` 只能**事后**抓主表与反向视图不一致;并发写互踩的代价是返工。② **定案**:每 claim 一个原子文件 `.atlas/runs/write-claims/<hash>.json`,`os.open(O_CREAT|O_EXCL)` 天然原子——并发 take 同一文件时后者退出非零并指名在办任务;`release` 即删(幂等;`--task` 不符拒绝释放他人 claim;空串拒绝);claim 键按 NFC+casefold 归一(大小写不敏感文件系统同一物理文件只有一把锁);EEXIST 后文件消失重试一次;残留 = glob 目录,`atlas_check` 新行 `写面残留` WARN 点名;claims 是运行态,`.atlas/` 不入库 ⇒ 不进 git 历史;`validate_e2e_index` 事后兜底保留不撤;旧装配无此命令 ⇒ 纪律照旧。③ **被否**:单 JSON 台账读改写(锁本身会被并发打穿,即**单 JSON 锁**);把 claims 搬进 git 历史做事实(运行态不是事实,即**claim 入 git**);用事后一致性检查替代事前仲裁(返工已经发生)。④ **钉与变异**:`tests/test_write_claim.py` **17 条**(含 8 进程并发 take 唯一赢家、大小写变体同一把锁、EEXIST 后消失重试、空串 release 拒绝、atlas_check 行 ok/WARN 两侧);M143(摘 `O_EXCL`)/ M144(摘残留 glob)四段协议,报告含注入 diff、还原逐字节一致。 |
|
|
610
|
+
| D141 | `validate_testids` 提取器**剥注释只做一次**(2026-10-23;`validators/validate_testids.py`、`tests/test_validators.py`、`shared/gates.md`;P5-A 性能批) | ① **病灶**:`collect_testids_expanded` 对每个源文件调用 `strip_comments` 两次(第一次循环取 `all_tids`、第二次循环取 `file_testid_decls`),同一份文本重复剥注释;隔离副本 profile 显示 `strip_comments` 3167 次 / 1611 文件、累计 0.238s,提取器占本门 95%。② **定案**:第一次循环就把 `strip_comments` 结果存进 `texts`,第二次循环复用;判据 / 输出不变。隔离副本 5 轮交错实测 796.5ms → 686.4ms(-110.1ms / -13.8%),输出逐字不变。③ **被否**:删判据省时间(页面集合 WARN 构造约 0ms、落点判据约 50ms,删了丢真实 WARN);并行化扫描(引入并发与顺序不确定性,当前不必要);按 mtime 缓存(跨轮脏读风险)。④ **钉与变异**:`tests/test_validators.py` 139 → **140 条**;`test_validate_testids_strip_comments_once_per_file` 数调用次数(每文件 1 次,修复前 2 次)+ 抽取值断言;变异 **M137**(还原第二次 `strip_comments` 调用 ⇒ 钉红;还原逐字节一致;注入 diff 见 `research/2026-10-23-ablation-p5a-mutation.md`);`shared/gates.md` 门行与测试计数同步。 |
|
|
611
|
+
| D142 | 新增 E2E **`request` 请求载荷断言动词**(2026-10-23;`rings/e2e/reference.md` §5.5/§7.1/§8.2;`scripts/gen_e2e_scripts.py`;`validators/validate_e2e_index.py`;`validators/validate_testids.py`;`tests/test_gen_e2e_scripts.py`、`tests/test_validators.py`) | ① **病灶**:断言动词只覆盖 DOM,无法验证「某提交请求的 JSON 载荷中字段缺席/等于某值」⇒ 只能人工开 DevTools;前端临时脚本用 `page.on('request')` 十行即可稳定断言,证明是 DSL 表达力缺口。② **定案**:`request <api> <json-path> <expected>`;api = `?api=` 的带点唯一后缀(歧义 FAIL);json-path 支持点号键/`[N]`/`[key=value]`,只有叶子可缺席;`(缺席)` / `null` / `""` 三态分离,`(空)` 拒绝;**动作锚定**:`after:N` 在第 N 步前记序号、无锚锚最后一步,只认该时点后**新出现的第一条**匹配请求(历史/自动保存/重试不得满足),无新请求 FAIL;中间缺失/过滤 0 或多命中/越界/请求体解析失败 = 解析错误;不参与零步基线;含该断言的用例默认 `trace: off`(`ATLAS_REQUEST_TRACE=1` opt-in),失败消息不打印载荷。③ **被否**:取「最近一次匹配请求」(历史请求可假绿)、`(空)` 合并缺失与 null、用 mock 替代请求断言(只能验渲染)、完整 JSONPath 依赖(零依赖契约)、允许用例写任意脚本。④ **钉与变异**:生成器“动作锚定 + trace off + 无 request 零变化”、Node 真求值三态/新请求/解析错误、校验器形态正反例、testid 提取不把 api 当 testid;变异 **M145**(摘 watcher 发射)/ **M146**(缺失叶子按空串)/ **M147**(接受无 step 的 request),四段协议还原一致。 |
|
|
612
|
+
| D143 | 人工走查项**结构化块**(扁平行式),id/title 由标题行提供(2026-10-23;`rings/e2e/reference.md` §8.2/§10.1;`_support/manual_block.py`;`scripts/e2e_console.py`;`templates/e2e/console/app.js`;`validators/validate_e2e_index.py`) | ① **病灶**:`manual.md` 只有 `- ID \| 页面 \| 标题 \| 一段说明`,控制台只能原样渲染 prose;步骤、命令、观察点、证据没有字段承载,UI 无法分步/复制,人工走查体验差。② **定案**:兼容旧行;新增 `## <ID> <标题>` + `atlas-manual` 块(`page / precondition / step / evidence`),`step` 每行 `操作 \| 观察 \| 命令(可空)`,复用 `parse_block` 扁平数组语义;id/title 只从标题行取,块内重写即 FAIL;解析单源 `_support/manual_block.py`(控制台/校验器共用);控制台按步渲染、命令可复制;账本仍保持用例级,不扩 schema。③ **被否**:嵌套 `steps` map + `command: \|` 块标量(需新语法/解析器,零依赖下脆弱)、逐步骤账本(扩账本 schema)、把结构化信息继续塞进 prose。④ **钉与变异**:控制台块解析/旧行兼容/块内 id 拒绝、校验器页面/步骤/重复 ID/解析失败;变异 **M148**(摘块解析或步骤校验),四段协议还原一致。 |
|
|
613
|
+
| D144 | `select` 唯一命中后点**打标元素本身**,废除整页 DOM 下标点击(2026-10-23;`scripts/gen_e2e_scripts.py` 的 `atlasSelect` 发射点;契约 §5.5 `select` 行) | ① **病灶**:旧实现采样时用整页候选集 `[role=option],[role=menuitem],[class*="select-item-option"]`,命中后返回 `idx = all.indexOf(hit[0])`,再点击仍按**整页同一选择器的第 idx 个**。一个面板可同时挂多个下拉(Ant Design 关闭态的 dropdown 节点仍留在 DOM,rc-select 另有零宽 `role=option` a11y 镜像),下标只对「采样那一刻的 DOM 快照」有效 ⇒ 组件一重排,同一数字指向另一个下拉的选项或不可点的镜像节点。现场(百炼 v3 测试环境,全量 41 条 2/2 红、单跑 2/2 绿):步骤要选「不限时间」,点击却解析成 `<div role="option" aria-label="自定义范围" id="rc_select_4_list_3">custom</div>`,重试 52 次 `element is not visible` 至 30s 超时;单跑时挂载的选项节点少、下标恰好还对得上 ⇒ **顺序依赖的假绿**。② **定案**:`atlasPick` 在页面内给唯一命中元素打一次性标记 `data-atlas-pick=<token>` 并返回 token(每次采样先清除全部遗留标记,保证标记至多一枚 ⇒ 选择器无歧义);点击改为 `page.locator('[data-atlas-pick="<token>"]')`,即点**命中的那个元素本身**,由 Playwright 走可见性/稳定性等 actionability 检查;元素被重排回收导致标记失效 ⇒ 重新采样打标再点,**有界 3 次**,仍不成则带可见选项清单响亮失败(不静默换目标、不静默通过)。就绪轮询(`expect.poll`)、三级降级匹配、歧义即拒、原生 `<select>` 分支与失败文案全部不变。③ **被否**:把点击作用域缩到「当前打开的 dropdown」类名(吃组件库 CSS 前缀,破坏实现无关契约,其他库仍会漏);按 label 用 `getByRole('option', {name})` 直取(antd 选项可访问名含描述文案,前缀/包含级联语义会退化,且多个下拉同名时无解);加固定等待等 DOM 稳定(D117 已否决固定等待,且治不了指错元素);把候选集限定为 `role=option`(D122 已证 antd 可见项无该 role,会退回「下拉已打开也采不到」)。④ **钉**:`tests/test_gen_e2e_scripts.py::test_select_clicks_the_marked_option_not_a_global_index`(① 病灶字符串 `all.indexOf` / `pick.idx` / `.nth(pick.idx)` 必须消失 ② 打标与按标点击成对 ③ 遗留标记清理行 ④ 有界重试 ⑤ 失败文案 ⑥ 仍用 `expect.poll` 且无 `waitForTimeout`);既有 D112/D117 两条钉按新形态更新(唯一命中返回 token 而非 idx、候选选择器出现次数由 2 降为 1)。**(编号说明:D143 已被人工走查结构化块占用,本决策顺延为 D144。)** |
|
|
614
|
+
| D145 | **装配基线 manifest**:`.atlas/MANIFEST.json` 逐文件摘要 + 配对校验 + 「未解释差异不得判一致」(2026-10-23;`RFC-20261023-02` ②;`scripts/pkg_digest.py`、`scripts/atlas_check.py`、`install.sh`、`shared/gates.md` §2.2) | ① **病灶**:聚合摘要只回答「变没变」,回答不了「变在哪」——`check_version` 的漂移 WARN 自证「差异明细跑 `backflow.py` 三态对账」;而外部流程规范给的机制是**逐项内容摘要 + 解释制一致性**(缺失字段 / 采集失败 / 未解释差异均不得判一致)。② **定案**:`manifest()` 与 `digest()` 共用同一次遍历产 `{digest, files}`;`stamp_file()` **先 MANIFEST 后 VERSION**(VERSION 在场即 manifest 已齐 ⇒ 半态可判定);`check_version` **配对校验先行**(manifest.digest ≠ 装配戳 ⇒ WARN 指名重装),配对后漂移**逐文件指名**(改/增/删各 cap 10 + 计数);旧装配无 manifest ⇒ 聚合口径 + ok 行尾注「正式判据未齐」(不新增红);两分支 WARN 保 M68 三词;`install.sh` 落戳后硬校验 manifest 在场;判定「一致」= 摘要相等 ∧ manifest 齐,未解释差异只能记「不一致(已解释)」(运行时仍 WARN,漂移是常态信号)。③ **被否**:扩 VERSION 为多行/JSON(旧副本全军 WARN,干跑守卫重走);漂移直接升红门(打断正常开发循环,解释制只属收口/发布判定);接线 `release.py`(过度设计,列为后续可选项)。④ **钉与变异**:`tests/test_tools.py` manifest 形态/配对/空装配三钉;`tests/test_atlas_check.py` 逐文件指名 / cap 10 / 不配套 / 形态非法 / 旧装配尾注五钉;F54 干跑扩 `MANIFEST.json` 逐字节断言;变异 **M-new-1**(摘 diff 分支)、**M-new-4**(摘配对校验)、**M-new-5**(摘干跑闸),四段协议还原逐字节一致。 |
|
|
615
|
+
| D146 | **域级 PRD 变更记录 5 列** + 「变更后只重验受影响项」通则(2026-10-23;`RFC-20261023-02` ③;`templates/prd/domain.md` §六/§七、`validators/validate_prd.py`、`rings/prd/reference.md`、`shared/global-rules.md` §16) | ① **病灶**:§七 只有「版本/日期/变更说明」,无影响、无结论与后续;「受影响才重验」原则散落 `apply` §3.2 / e2e 收口三值② / D128 `stale` 三处而未收拢,变更后哪些结论可沿用无处承载。② **定案**:表头升级 `版本/日期/变更内容及原因/影响/结论与后续`;「影响」点名受影响域/AC/用例,未触达验证面写 `无(未触达验证面)`;validator 独立 check 行:5 列 PASS / legacy(含`变更说明`)WARN「下次真实变更时随迁」/ 表头不可辨或「影响」空 FAIL / 节在无表 WARN;通则落 `global-rules` §16(只重验受影响项、未受影响结论沿用并留痕),机器落点指认 D128 stale / `backflow` 三态 / apply §3.2,不新增门;全局 `十一` 修订记录 4 列**不扩**(边界句)。③ **被否**:语义塞进「变更说明」单元格(无判据);新增「变更→受影响用例集」对账门(语义依赖,先立字段与通则);全局修订记录同步扩列(无消费者)。④ **钉与变异**:`test_rfc20261023_02_change_record_shape_and_impact_column` 四态;**M-new-3** 删 legacy 分支 ⇒ 钉红;测试夹具随迁 5 列(不为迁移单跑一轮)。 |
|
|
616
|
+
| D147 | **显式「无」通则** + `FORBIDDEN` 三处单源 + 两处机器检查首版 WARN(2026-10-23;`RFC-20261023-02` ④;`shared/global-rules.md` §5、`validators/validate_prd.py`、`validators/validate_structure.py`、`templates/prd/domain.md`、`rings/prd/reference.md` §7、`rings/e2e/reference.md` §10.1) | ① **病灶**:`无` / `不适用(理由:…)` / `未覆盖(沿用实然)` / 控制台「未创建」四套写法并存,「空表/空节」与「评估过为无」同形;`FORBIDDEN` 不抓空表,也不抓 `[必填:…]` 占位残留;两校验器与 §5 的词表各说各话。② **定案**:通则 = 「可无内容不得留空白,必须有结论」(`无`(理由可选)/既有语义词,词表单源 = §5);`FORBIDDEN` 增补 `[必填` / `[条件必填` 并三处同步 + 守恒钉 `test_forbidden_vocabulary_single_source`;机器落点只两处(§六 空表/空节无 `无` ⇒ **WARN 首版**;§七「影响」空 ⇒ FAIL),均独立 check 行、登记门册;模板占位指令统一「无则写 无(理由可选)」;翻转触发器 = 连续两收口周期真实遗漏 ≥1 且误报 0 ⇒ 升 FAIL,统计落 per-域收口留痕 README「可无项空表:N / 误报:N」。③ **被否**:全量扫描所有可无节 FAIL(违反「落门先问语料」);把四词语义合并成单一写法(丢语义边界)。④ **钉与变异**:`test_rfc20261023_02_optional_section_explicit_none`(空节 / 空表 / 显式 `无` / 开户最小集四态);**M-new-2** 判据恒真 ⇒ 钉红。 |
|
|
617
|
+
| D148 | **真跑日协议**:一次「重装配 + 真跑」把已立待咬的能力一次咬合,**detached worktree 隔离执行**(2026-10-24;runbook = `docs/guide/real-run-day.md`;维护者拍板「都按你推荐的来」+ 边界「v3 用户分支 `dev_20261120` 不碰、自建 worktree、不提交」;源自设计优化方案 v2 WS-B,评审结论已并入) | ① **病灶**:咬合点分散且都挂在「下一次真跑」上(D108 留证 / adapters 面 / hover 与 goto 未解观察 / WARN 基线 / D80② 升档 / 副本漂移正式解法),逐个等真跑 ⇒ 凑不齐;且用户 checkout 常被占用(现场:v3 `dev_20261120` 在用)。② **定案**:runbook 六步(worktree 隔离 → 重装配 → D145 manifest 配对校验 → 重生成对账 → 净场协议 → 真跑留档 `product/e2e/reviews/<日期>/`)+ 七项咬合清单(逐项勾、咬合一处回写一处:gates 对应行 / bugs.md / QUEUE)+ **注红条款**(注红 = 取证动作,red 不计缺陷判据、不进 bugs.md 复发计数、不触发巡检升判——与「首跑不坐实」纪律的摩擦预先写死)+ 不做清单(不顺手改用例语义/产品代码、不在用户 checkout 留痕、不提交 worktree 产物;登录态只读复制不覆盖)。③ **被否**:逐项单等真跑(凑不齐);用户 checkout 直跑(分支占用 + 痕迹);GUI/看板类真跑仪表(运行时平台机制,Atlas 零运行时)。④ **钉与变异**:本条为协议页 + 决策行,无新机器面(咬合证据回写既有登记面);D149(app_login 升档)与 D108(失败留证)是首批两个咬合对象。⑤ **出库判定(2026-10-25 首轮执行完,出库)**:七项结果——① D108 两面咬到(红 28 构件/绿零构件;**附带三发现**:落点勘误〔`.atlas-run/` 非缺省 `test-results/`〕、留存窗口〔Playwright 起动清空 outputDir,观察入 bugs.md〕、「栈起没起」探针盲区首个活体实证〔前端 200/后端不可达⇒门放行全红〕);② adapters 面实况已采集(models 渲染层 82/1859 行、routes 19/55——与既有记录同族,无升档动作);③ hover 未咬到(靶场白屏无从复现);④ goto 计数未咬到(同因);⑤ WARN 基线实测 **7 条**(≥3 且形态重复 ⇒ 观察室升室条件满足 ⇒ QUEUE 待拍板案 9);⑥ D149 已声明面咬到;⑦ worktree 面咬到(首装即最新 + D145 manifest 配对 ok;用户 checkout 漂移副本未动,留用户自行 `--update`);D124 就绪门顺带 ✓(chromium launch 实跑)。协议页随首轮实况修订(落点/留存窗口)。 |
|
|
618
|
+
| D149 | **D80② 升档兑现:`e2e.app_login` 未声明 ⇒ 生成器推 `NOT_READY`(退出码 3 + 机读键 `"app_login"`)**,不再「占位生成 + WARN」半生成(2026-10-24;RFC-20261015-02 预告的时间锚「下一次真跑准备日」到期执行;真跑日批) | ① **病灶**:占位形态 = 硬前提的输入被「缺失即静默放行」吸收(未登录态跑用例 ⇒ 假红/假绿都解释不了)。② **定案**:与 `app_base_url` 同一通道族(`--json` 面 `{"ok": false, "not_ready": "app_login", "message": …}`);文案按 **D103 相抵点**指路——「有门但三态都装不下」(图形验证码 / 服务端 cookie 会话 / 登录响应体无令牌)⇒ 人工会话取出,是描述路径、不是「补声明」的对象(排除类不被罚 not_ready,出路写进文案);`atlas_check` 消费面走既有 `GEN_NOT_READY` 三态 SKIP 中继(message 透传,无需改);`validate_stack_profile` WARN 保留(两道机器面都看得见)。③ **被否**:直接 FAIL(「未走到这一步」≠ 失败,`3509 §B110`);文案只说「请补声明」(丢 D103 排除类出路 ⇒ 把「如实未声明」罚成必填错)。④ **钉与变异**:`test_setup_without_app_login_pushes_not_ready`(改判自旧钉「degrades_loudly」:rc=3 + stderr 指路含 `mode: none` 出口与「人工会话取出」+ **不写盘**)+ `test_app_login_not_ready_json_face_matches_channel`(机读键 + message 面);变异 **MB17**(退回 WARN 半生成 ⇒ 红)/ **MB18**(机读键写错/缺 message ⇒ 红)。⑤ **出库判定(2026-10-25 出库)**:已声明靶场(v3 fabricated)行为不变实证——真跑日 regen 正常、setup 预置照跑、无 not_ready(D148 首轮第 6 项);未声明面由钉 MB17/MB18 守。 |
|
|
619
|
+
| D150 | **验收执行器 = 执行层最小立项面 ①**(2026-10-25;`proposals/RFC-20261025-02` v2 + 评审 `REVIEW-20261025-02`;前置链 = R-1 表态 → RFC-20261025-01 前置研究(两道闸)→ 三张清单(评核过);维护者「继续」拍板进实现批,同日落刀) | ① **病灶**:真跑验收六步是 runbook 非机器行为(prepare 调用/结果汇总/留档/断点续跑靠纪律);runbook 六步中唯一无机器承载的 = 编排 + 统一报告 + 留档三件事(评审建设性判定)。② **定案**:`scripts/atlas_verify.py` 四步——pre(项目钩子 `atlas-prepare.mjs`,cwd=项目根、180s、**批级 `ATLAS_CASE_ID=verify-batch` 显式批标识**〔新声明:非用例 ID,逐用例分段仍归控制台〕、`node_modules` 存在 ⇒ 注入 `NODE_PATH`);**run = `atlas_check` 默认模式为执行体(全批只跑一次真跑——v1 评审 MUST:双跑 = 首跑 D108 留证被 Playwright 二次起跑清空;`--fast` 分支被否因门行缺席)**;report(stdout/`--json` 沿行形态 + 留档 `reviews/<日期>-verify/` **仅全链绿落档**——失败批不落档故重跑不被短路);backfill **缓行**(写面零触碰:不写 e2e-index、不进评审账本、不取 claim——第二写手四题另案)。续跑语义无独立状态文件:留档在 ⇒ 整批短路;pre 恒重跑(如实);run 按 `results.json` 产物判(新鲜度依赖 Playwright 起跑清空行为,明写)。失败停环沿 apply §7 条文化。③ **被否**:执行器自起 Playwright(双真跑);独立净场步(净场已机制化于 prepare,D110);复用 `ATLAS_REFRESH_SETTLE` 当静置窗(换用途复用旋钮);独立状态机文件(死结同族);零变化钉的「全仓逐字节」口径(收窄为既有套件全绿 + 行集零新增红)。④ **钉与变异**:`tests/test_atlas_verify.py` 七钉(pre 失败停环 / 批级 ID 值形态 / run 失败含门行+留证路径 / 留档短路 / 断点语义·pre 恒重跑·run 按产物 / **全批恰一次真跑** / 参数错不写盘);变异 **MB19**(摘留档短路)/ **MB20**(摘 run 续跑判据)四段协议双咬、还原逐字节一致。⑤ **出库判定(2026-10-25 当日出库——合成靶场机制面咬合)**:零后端依赖的真跑咬合于合成最小采纳仓(`/tmp/atlas-verify-playground`:beta.8 装配 + 本地静态页 + `mode: none` + 最小合法 PRD 组)完成:全链绿批(pre 真 prepare / run = atlas_check 真跑 **3 passed 真浏览器** / report 留档 `reviews/2026-10-25-verify/`)+ 短路复跑 + 注红批(产品态破坏 ⇒ run FAIL + trace 留证 + **不落档**)+ 恢复复绿重落档。**咬合附带两项修正与两项真发现**:① **假绿通道缺陷(现场实证即修)**——②定案中「run 按 `results.json` 产物判」在失败批同样成立(Playwright 失败也写 results)⇒ 失败后重跑会跳过 run、把空行集落档成 ok(playground 旧副本注红批活体演示)⇒ 修正为 **run 恒重跑、留档是唯一短路门户**(钉⑤重写 + MB20 重定向「摘失败停环」双咬);② 装配副本钉与实现未同步拷贝 ⇒ 对齐测试红(钉-实现同批纪律再证);③ **map 快照/新鲜度门缺「缺席 ⇒ SKIP」守卫**——beta.8 装配进无 docs/map 采纳仓必红(playground 实测 2 挂),已补守卫;④ beta.8 发版漏刷 docs/map 版本串与计数(「始终最新」门在自家发版上咬红)——已刷;release.py 版本同步面不含 docs/map,列待办。v3 业务现场 41 用例全绿轮 = D148 首轮余项(等测试环境后端),**不属于本条出库条件**;实现批验收线一句话版在 RFC §4 尾。 |
|
|
620
|
+
| D151 | **执行器 backfill 写面 = 执行层最小立项面 ②**(2026-10-26;`proposals/RFC-20261025-02` 附录 B v2 + 独立评审 `proposals/REVIEW-20261026-01-backfill-write-surface.md`〔tech-proposal-reviewer 只读,总判定「修后通过」,5 必修全落;评审基线 af3d6f2〕;D150 缓行四题的解) | ① **病灶**(评审咬出的两刀):机器标记锚在索引 `状态原因` 列——与 `e2e_console.writeback` 对 green/red 硬清空该列(:829-831)、`validate_e2e_index.py:373` 配对规则(原因非空 ⇔ blocked/skipped)、`validate_e2e_index.py:499` 词表闭集三方正面互斥,按 v1 措辞每次机器回填必触发「退回人工通道」,附录整体落空(「判据写在判据改不动的表面上」,自感知上报族后继);撞窗处置「请重跑」是死路——若回填排在留档后,重跑被留档短路(atlas_verify.short_circuit exit 0,回不到 backfill 步),且「作废」无修复动作定义。② **定案**:步序 = pre → run → **backfill** → report 留档(回填插 run 后、留档前 ⇒ 留档 ⇔ 全链绿**含回填成功**,短路门户语义不变;红批不回填——失败停环,回填窗口语义单一 = 仅全链绿批);**B1 单一来源** = 复用 `e2e_console.writeback` 同函数(签名/行为零改动;导入 = `sys.path[0]` 装配态同目录〔源包态 `scripts/`、采纳态 `.atlas/scripts/`,install.sh:440/448 同批拷入〕与控制台同副本同版本、无安装副本漂移;`results.json` 解析 = `suites[].specs[].title` 首词为用例 ID,`expected/flaky` ⇒ green、`unexpected` ⇒ red、`skipped` 不入 draft 不回填;**机器标记锚 = 留档**——report.json 回填章 + README 声明行,索引行/校验器词表/配对规则零改动,行级机读标记将来 ⇒ 显式另案〔词表+配对+writeback 三处同步〕;**不提炼 `_support/`**——一次搬家仪式价 > 一次 import 耦合,导入契约入文);**B2 `events.jsonl` 永不写入**(评审账本 = 人签字载体,D127/D128 语义;console 无「导入执行器结果」通道,人批 = console 逐条重执行;指纹保证评审新鲜度,不搬运结果);**B3 claim**(`scripts/write_claim.py`〔装配态 `.atlas/scripts/write_claim.py`〕,task 固定 `verify-backfill`,粒度 = 整文件 `product/e2e/e2e-index.md`,失败路径也 release 不产残留)+ 撞窗基线写死 = **events.jsonl 的(字节数/行数 + 最后一条 submit 事件的 (ts, prov))**,写后复检同一基线:尾 submit 变 ⇒ **撞窗** = exit 非 0 + 结构化差量(意图改动逐行 + 写后索引现值 vs 意图值)+ **出路 = 人工经 console 重提交**(非「请重跑」:重跑 = 全量真跑,D108 留证被清);字节变而尾 submit 不变 ⇒ **区间漂移**注记不阻断(索引未被竞写——submit 是唯一写索引的账本事件);与 D128 stale 分工边界 = 渲染期人审新鲜度 vs 批期活着检测,e2e-index 不在 `scripts_hash` 白名单 ⇒ 执行器自写不致人审误 stale;竞写窗披露不变(console submit 不走 claim,检测式缓解;根治 = console 接 claim ⇒ 另案);**B4 三写前硬断言**(① draft 键集 ⊆ 索引行 ID 集——writeback 对未知键静默丢弃,须执行器自己数 / ② 留档键集 == 改动集 / ③ 步序保证 report.json 含回填章),断言不过 ⇒ exit 非 0;「退回人工通道」= 路由建议,非可判定条件。③ **被否/采纳**:v1 状态原因注 verify-batch(机器不可满足,必修 1);提炼 `_support/`(YAGNI);撞窗「请重跑」;B2「指纹支持人再提交同一批」不实措辞;红批回填;评审建议项全采(签名不变声明 / 机器绿行与人批在索引内不可分辨·分辨面留档 / 红批不回填 / 钉手段指定 / verify-batch 一物两用注记)。④ **钉与变异**:`tests/test_atlas_verify.py` 7 → **10** 条——⑧ B1 同函数(进程内 import 全等:模块解析面向 `scripts/` 兄弟 + 记录器证回填以 `apply=True` 经 `e2e_console.writeback` 而非内联改写)/ ⑨ B2 账本零变化(回填前后 events.jsonl 字节不变 + 留档章键集 == 改动集 + README 声明行 + claim 无残留 + 留档短路批不再跑)/ ⑩ B3 撞窗(回填窗口内注入伪造人批 submit ⇒ 尾 submit 变化被复检抓到 ⇒ exit 非 0 + 撞窗报告含意图/现值 + **不落档**);变异 **MB22**(回填退回内联改写 ⇒ ⑧红,连带⑨)/ **MB23**(摘写后复检 ⇒ ⑩红)四段协议双咬、含注入 diff、还原逐字节一致(原始输出 `.atlas/runs/mutation-proof/`,运行态不入库)。⑤ **真跑咬合** = 下一次真跑日;当场验收 = playground 批后索引状态列 ⟷ `results.json` 一致 + 账本零变化 + 撞窗注入作废路径(`tests/test_atlas_verify_playground.py` 常驻回归)。 |
|
|
602
621
|
|
|
603
622
|
|
|
604
623
|
|
|
605
624
|
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
| D152 | **项目级资产根可配置 = `atlas.config.yaml` + `_support/layout.py` 单源解析**(2026-10-26;`proposals/RFC-20261026-01` v2 + 独立评审三轮「修后通过」;issue #42) | ① **病灶**:`validate_adr.py` / `atlas_check.py` / `install.sh` / `templates/stack-profile.yaml` / `extensions/pi.ts` / 写侧模板把项目级资产根写死 `product/`,存量 adopt 项目已有权威 `docs/adr` 等布局时无法对齐单一真相源;改 `.atlas/` 副本不可持久(包产物会被 update 覆盖)。源包实测:Python/Shell 生产侧 21 个文件、契约 131 行、25 个测试文件耦合;issue #42「只改常量后 9 passed」在隔离副本复现为 **9 failed**(夹具/断言写死 `product/adr`)。② **定案**:项目根 `atlas.config.yaml`(git 跟踪,`version: 1` + `layout.assets_root` 默认 `product` + 可选 `layout.adr` 默认 `<assets_root>/adr`)是唯一布局锚点;`_support/layout.py` 严格解析(路径逃逸 / symlink / 未知键 / 重复键 / 版本非法 ⇒ `LayoutError`)并派生 stack-profile / prd / e2e / design / reviews / reports / console 台账路径。全部机器消费者(10 validators / 11 scripts / 4 adapters / 2 support)、`install.sh`、`apply-wiring.py`、`extensions/pi.ts`、`closeout_derive` 事件前缀、`refresh_structure` 脏树豁免、`make_review_inputs` 默认值统一走该解析器;模板 e2e 三路径用 `{{ASSETS_ROOT}}` 占位、首装渲染;`atlas init` 首次 TTY 询问(默认 product)或 `--assets-root` / `--adr-dir`,`atlas update` 只读配置绝不换根;首装所选根非 product 且默认 `product/` 已有 Atlas 资产 ⇒ 拒绝(双根);非法配置 ⇒ `atlas_check` 单行 `项目布局配置` FAIL 且 update 零写;双根 / profile 路径漂移首版 WARN。v1 只开放 `assets_root` + `adr`,e2e 三路径继续由 `stack-profile.yaml` 显式覆盖。③ **被否**:配置放 `product/stack-profile.yaml`(引导鸡生蛋)/ `.trellis/config.yaml`(Trellis update 覆盖)/ `.atlas/`(包刷新覆盖)/ 仅环境变量或 CLI(不可版本化)/ 自动探测 docs(隐式歧义)/ 符号链接(git 前缀消费者漏触发)/ 只做 ADR 单点(两套机制)/ 直接改名全局默认(破坏存量)/ 每次 install 都提示(破坏 CI)/ 生成 `.atlas/LAYOUT.json`(视图漂移);v1 选 YAML + helper,不引入 JSON 解析分叉。④ **钉与变异**:新增 `tests/test_layout.py` 33 条;`test_validate_adr.py` 11、`test_atlas_check.py` 45、`test_install_selfcheck.py` 10(含 docs 根首装 / update 只读 / 非法零写 / 双根拒绝)、`test_gen_e2e_scripts.py` 109、`test_refresh_facts.py` 21、`test_closeout_derive.py` 15、`test_e2e_console.py` 25 等 override 钉;守恒测试含 Python AST + shell/TS 正则(`product/` 路径构造除 `_support/layout.py` 与白名单外禁止)。隔离副本变异 **M-layout-1..5**(validate_adr 回退硬编码 / check_layout 非法配置放行 / install.sh 模板回退 product / refresh 豁免回退旧常量 / closeout ADR 前缀回退)均红→还原逐字节一致→复绿。⑤ **真跑**:临时项目 `install.sh --assets-root docs` 生成 `atlas.config.yaml` + `docs/stack-profile.yaml`(e2e 三路径 docs),无 `product/`;`--update` 配置与资产逐字节不变;非法配置 update exit 2 零写;双根首装 exit 2;重复 init 命中既有 `.atlas` 守卫 exit 1;非 TTY `atlas init` 默认 product;全量 `pytest tests/` **704 passed / 4 skipped**,playground 真跑 **2 passed**。 |
|
|
628
|
+
| D153 | **后端通道 AC × 覆盖维度对账 = PRD 第 5 列 + `dims=` + WARN 门**(2026-10-26;`proposals/RFC-20261026-02` v4.1 / Batch A;独立评审 `REVIEW-20261026-05`「修后通过」;`validators/validate_ac_alignment.py`、`_support/ac_coverage.py`、`scripts/atlas_check.py`、`scripts/atlas_verify.py`) | ① **病灶**:后端/全栈 AC 可声明覆盖维度,但 `validate_ac_alignment` 只收 AC ID、不读维度;E2E 门只读第 4 列 ⇒ 后端维度声明无对账面。② **定案**:PRD §五 新增可选列 `后端适用覆盖维度`(按表头名定位;3/4 列 N/A;错位/备注含维度形态 ⇒ WARN;逐 PRD/逐表聚合;模板占位留空/N/A);测试侧 `@pytest.mark.ac(..., dims=[...])`(精确 C01-C14;只作用于 marker AC;docstring 不参与;多 AC/空/非字面量/未知/孤儿 ⇒ WARN);后端门只 WARN(缺维度/仅弱证据/零测试 ⇒ WARN);通道 = backend/fullstack app + `backend_test` + pytest + test_dir,多后端/不可用 ⇒ N/A;`Result` 加 N/A(早退 N/A),`atlas_check` 子项 `[N/A ]` 进 detail、整门 N/A 才行级 N/A,`atlas_verify._parse_rows` 接受 N/A;`_support/ac_coverage.py` 为维度解析单源(E2E 纯抽取,D130 行为不变);`backend_test.mode` 只管 AC 挂载,维度恒 WARN。③ **被否**:跨通道并集(UI 缺口被后端吞)/纯后端收窄(全栈缺口)/单列内联限定(新语法)/新 marker(改 apply-wiring)/docstring 声明/独立 manifest/复制 parser/直接 FAIL/B01-B14。④ **钉与变异**:`tests/test_validate_ac_alignment.py` **39 条**(后端 32 + 既有 7)、`tests/test_atlas_check.py` 45、`tests/test_atlas_verify.py` 11、`tests/test_shared_kernel.py` 12;**M156a-f**(dims 解析 / 缺失聚合 / 零测试 / weak-only / 通道闸 / 本地 parser)四段协议全咬;全量 `pytest tests/` **743 passed / 4 skipped** + `install.sh` 空目录探针 exit 0。⑤ **边界**:v1 单 `test_dir`、pytest only、`test_*.py` only、无 app 维度;E2E 第 4 列表头/weak-only 归 Batch B(另立 RFC)。 |
|
|
629
|
+
| D154 | **bug 台账条目级机器字段 = 「已知 bug 资产化」最小落地面**(2026-10-26;`proposals/RFC-20261026-04` v2 + 独立评审 `proposals/REVIEW-20261026-07`〔tech-proposal-reviewer 只读,总判定「修后通过」,4 必修全落 + 建议项全采;评审基线 d762ea4、试跑基线五段冻结〕;2026-10-26 发版雷达手动补跑首送〔外部来源 = OpenHands v1.28.0 `verify-openhands`「executable known bugs + 可重复修复证明 + 共享 known-bug 契约」;**同场②条 claude-code 钩子 `onFailure:"block"` 撤案**——评审前置代码取证发现三面已全覆盖/已兑现有档:D110〔失败即拒 + `prep_failed` 如实上报 + M94〕/D124〔就绪门 FAIL 指名安装命令 + M124/M125〕/D150〔pre 失败停环 + MB19/MB20〕,外部互证不立项〕;维护者拍「A」照 v2 落刀) | ① **病灶**:DR4 口径 B 判据(归纳在案 ∧ 案数≥3 ∧ 近 30 天有新案)的机器实现只能从散文段近似——族=段、案数=段内 `#NN` 上界、日期=段内任意文本最大日期串(`families.py:26-49/64/80-81`);README 明置三条盲区(跨段族机不可见 / 段漂计数 / 无日期字段)。实病形四型:陈旧收尾说明日期抬活跃窗(前瞻性失真——现行基线五段无翻面试跑实证)、新案漏日期即「静默」(`render` 自认「无日期 ⇒ 视为已静默」)、D131 证明件出库后台账无引用锚断链(育成 "共享 known-bug 契约" 真缺口 = 条目↔证明件机读面)。② **定案**:`bugs.md` 新条目必带全角尾缀 `〔日期|状态|族|证〕`(闭集键;坏形状 = 运行面 WARN + 按无字段回退,**不静默**——防「静默吸收」族);`families.py` 日期来源**分层**(带字段条目 = 字段日期;无字段条目行 = 行内散文日期;段头/归纳行/收尾说明等其余行不再抬窗——(a) 计量字面不变性保留);**判据三项与「案数上界」计量基准原样不动**(评审必修 1 采 (i):上界 ⇄ 精确的重议 = 对 10-17「口径 B」拍板的重新解释,**必须 DR1 另案**,旧行号 = README §2 DR4 + `DESIGN.md` D102;本批只做显示列);`render` 增「精确病案数」展示列(带字段条目 `#NN` 自身去重,不参与判据);`族` 字段只解析记录、不参与判据(升档条件 = 库内第 2 个跨段同族实例,grep 可判);`证` 锚优先级 = D 决策行 > M 变异号/钉名 > 留档路径辅注(`.atlas-run/` 每起跑被 Playwright 清空 = 易腐路径实证 bugs.md E2E 段「留证留存窗口」观察);§3 五件同批(README §2 制式小节+盲区收窄 / README §4 入账条款〔明示 = 巡检 prompt 仓内裁决面〕 / bugs.md 头部制式+两套括号标记归属 / families 六钉 / `duty_signals.sh` **零改动**——唯一接点 :30 只调 families.py)。≥3 条 `证:` ⇒ 缓行升档条件(grep 可判)。③ **被否**:判据计量直接精确化(须 DR1,本批不自授权);JSON/SQLite 搬介质(「库是工作面不是台账」,README §4 原话);独立 validate_bugs_ledger 校验门(非流水线产物,形状纪律归钉 + WARN);`证` runner 进巡检链(只读铁则);旧条目 git 考古回填(不改变任何判据结果,只吃成本与失真源)。④ **钉与变异**:`tests/test_duty_bug_families.py` 4 → **11** 条——字段四型解析钉 / 坏形状 WARN+回退钉(5 坏形 + 行内散文回退实证)/ 半字段合法钉 / 混合段并集聚合钉 / 显示列出缺钉 / **现行库基线冻结对照钉**(2026-10-26 五段 verdict/案数/最近日期逐段冻结 = 字段制式对旧库零扰动实证);既有真值表/形状钉双绿未变;变异 **M157a**(摘分层日期来源 ⇒ 回全行散文日期 ⇒「陈旧说明不抬窗」正向钉红)/ **M157b**(摘坏形状 WARN ⇄ 静默归零从另一门归来 ⇒ 钉红),四段协议含注入 diff、还原逐字节一致,原始输出 `.atlas/runs/mutation-proof/`(运行态不入库)。⑤ **真跑/咬合**:无独立咬合点(台账为工作面非流水线产物;咬合 = 当日 + 下一例实地落账即第一现场——新案条目按 `〔日期 …〕` 落账即为机制首咬)。 |
|
|
630
|
+
| D155 | **台账字段日期越界校验 = 日历关 + 未来关(「判据可被形态满足」族复见落刀;RFC-03 Phase 0 试点)**(2026-10-26;`proposals/RFC-20261026-05` v3 + 独立评审 `proposals/REVIEW-20261026-08`〔轮 1 否「换日期串当留出」、轮 2 否「换调用通道当留出」、轮 3 复核 v3「修后通过」〕;维护者拍「按建议来」) | ① **病灶**:D154 字段日期只过形态正则 `^20\d\d-\d\d-\d\d$`——`2026-13-40`(日历非法)/ `2027-01-01`(晚于当日)被当合法且**静默**抬活跃窗(未来日期 ⇒ 段永久「升室候选」,零 WARN);契约分叉:RFC-04:60/116 写「日期越界」、canonical README §2 与机器面只做「形态」⇒ 自感知上报族「判据可被形态满足」跨段复见(`bugs.md:60`,行号以 D155 工作区为基)。② **定案**:`_tail_fields` 形态过正则后再过两关——**日历关**(`date.fromisoformat` 失败 ⇒ 坏)+ **未来关**(`parsed > today` ⇒ 坏;`today` 由 `families()` 恒透传,直接调 `parse_sections` 不传 ⇒ 只校日历 = 已知降级面,docstring 明写);均走既有坏形状出口(WARN + 按无字段回退),不造第二语义。真实库**条目区**非法 / 未来日期 **0 条**(全文 6 个 distinct 日期含头部制式块坏例 1 个,落非条目行不参与取数 ⇒ 「全文 0 条」为假,不得如此写)⇒ 冻结基线零扰动(两版 md5 均 `7a461f99…`)。散文日期不校两关 = 显式残留盲区,载体 = `duty-review/README.md` §2 口径 B ③(升档条件 = 出现一条因散文非法 / 未来日期判面翻错的实例 ⇒ DR2 另批)。③ **被否**:散文日期同批扩校(改 D154 既有回退语义、存量收益 0);只 WARN 不回退(响亮成装饰);时钟容差配置键(投机灵活性);独立 `validate_bugs_ledger` 门(D154 备选 C 已否);改写 D154 既有钉(混入他人批次,判别补强一律新增)。④ **钉与变异**:`tests/test_duty_bug_families.py` **11 → 18** 条(v1 4 + 轮 2 后 v3 补 3)——日历非法钉 / 未来日期钉 / **合法边界反面包**(防「只挡越界」写成恒红)/ 判别性补强(尾注**晚于**字段日期,既有钉 #1 咬不住,**新增不替换**)/ **归零路径钉**(段内字段日期全越界且零散文日期 ⇒ `last_date=""` 走「无日期⇒已静默」分支且 `exact=None` 不虚计;既有坏形状钉总留一条带散文日期的行,从无一条走到此路)/ **`T` vs `T+1` 紧边界对**(G2 用 2027 远日期,只按年比较的偷懒实现能骗过它)/ **CLI 送达钉**(`main()` argv → 读文件 → WARN 抵达 stdout = `duty_signals.sh:30` 的真实消费面;日期由真实当日推导 ⇒ 不绑死某天)。**三条 v3 钉在 D154 版上实测全红(`/tmp` 副本 `3 failed`,不碰仓):归零钉 = 判据级留出(verdict / `last_date` / `exact` 三层翻转;夹具不可由既有夹具只改日期串得到);`T+1` 紧边界 = 判别性补强、不冒留出名(轮 3 年粒度变异实测:`parsed.year > today.year` 下 G2 绿、唯它与 CLI 钉红——它走的是与 G2 同一条未来关分支)**。变异 **M158a**(摘日历关)咬 2 条 / **M158b**(摘未来关)咬 4 条 / **M158c**(`print`→`stderr.write`)**恰咬 1 条 = 只 CLI 钉、17 条判据面全绿**——红集恰等于依赖被摘那关的钉,无无关钉牵连;四段协议含注入 diff、还原逐字节一致,报告 sha 已与交付件对齐(首轮记的是改 docstring 前的旧 sha,轮 2 Major 4 指出后重跑),原始输出 `.atlas/runs/mutation-proof/M158{a,b,c}.txt`(运行态不入库);`duty_signals.sh` 零改动(唯一接点 `:30` 只调 `families.py`)。⑤ **试点咬合(RFC-03 正式 Phase 0,经三轮评审才站住)**:轮 1 否 v1——留出 `2026-02-30` 与缺口 `2026-13-40` 同段形只差日期串 = §2.1.4 明列的「同 fixture 改措辞」,且 pre 红来自按探针新写的钉、留出只有 post 记录;轮 2 否 v2——改走 CLI 通道后核算「同输入下 stdout 结论由 `_tail_fields` 唯一决定,通道不新增判据级代码」,只承认它值得钉**送达**面(实测 `print→stderr` 时 15 钉全绿而告警消失)。**v3 的合格证据 = 归零路径留出(pre 红 → post 绿,判据级)+ `T+1` 判别钉**;归因如实剥离:11 段矩阵 7/11 判定变化中**仅 2/11 属 D155**,其余 5/11 属 D154(pre=`d762ea4` vs D154 即 5/11)。「日期越界 ⇒ 坏形状 WARN」子声明至此**具备闭族证据**;但按本机制自定规矩(试点结果须独立评审确认、不得自评代过),**轮 3 复核判「修后通过」:B1(本行补全)+ M1(留出口径降格为「一条留出 + 一条判别钉」)+ M2(触发 (b) 手工对照落盘)修完即判「通过」**;Phase 1(`proposals/README.md` / `TEMPLATE.md` / `gates.md` §2.5)未启动。三轮驳回的都是证据学说法、非代码实质——说法降到证据能撑住的位置。口径与复算见 `proposals/RFC-20261026-05` §4.2/§4.5/§4.6,评审链见 `proposals/REVIEW-20261026-08`。 |
|
|
631
|
+
| D156 | **系统性修复的留出验证 = Phase 1 制度化(`RFC-20261026-03`)**(2026-10-26;`proposals/RFC-20261026-03` v4 + 独立评审 `proposals/REVIEW-20261026-06`〔轮 1/2 修后通过、轮 3 试点确认〕;试点 = `RFC-20261026-05` / `REVIEW-20261026-08` 轮 3「修后通过」;维护者拍「继续」) | ① **病灶**:系统性修复(改门 / 校验器 / 契约判据)合入只靠「同批测试绿 + 变异钉」,不证明换一批未参与挖弱点的案例仍有效——「修好了」与「没人再撞到」不可分(自感知上报子族跨段复发实证,`bugs.md:60`)。② **定案**:触发 (a) RFC §1 引子族锚或 ≥3 条独立审查重复 finding;(b) 改门 / 校验器 / 契约判据且 diff 落在 DR4 段级锚路径族(独立评审用 `git diff --name-only` + `closeout_derive.py` 路径族表手工对照,记映射与误报)。触发者 RFC §4.5 必写留出集 + pre-fix 红证 + post-fix 绿 + 反面包 + 三态 `闭族` / `未闭族` / `单案修复(无留出集:评审确认 + QUEUE 观察行 owner/期限 + 残留风险)`。**留出独立性三条**(缺一按「同夹具变体」驳回,独立评审当场重放、不由作者自证)= ①判据输出翻转(verdict / 案数 / 最近日期 / 精确病案数;仅 WARN 文本 / 日志 / 通道变化不算)②变异增量杀伤(既有钉全绿而留出案红)③结构不可达(不可由既有钉夹具只改日期串 / 通道 / 增删行得到)。唯一判定记录 = 那份 RFC §4.5 + 其 REVIEW(不新增台账);`bugs.md` 指针行 `- 留出验证:RFC-… / REVIEW-…`(无状态 / 无日期 / 无 `#NN`)。落点 = `proposals/README.md` §2/§3、`proposals/TEMPLATE.md` §3/§4.5、`duty-review/README.md` §2/§3/§4、`shared/gates.md` §2.5 流程门行(语义项、无机器判据)、`shared/independent-review.md` §9(触发 (b) 检查项)、`shared/closeout.md` 步骤 3(作者侧定档落点)。③ **被否**:每份 RFC 强制留出(一次性修复无族可闭,过宽);自动 LLM 聚类 / 自动改契约(过拟合、门禁不信模型自评);Spec Fidelity 百分比硬门(数字未核);先制度化再试点(违「先量再动手」);继续用「无新案」当闭族(无证据 ≠ 已泛化)。④ **钉与变异**:`tests/test_duty_bug_families.py` **18 → 19**(留出验证指针行形状钉:正向合规指针不改三值 / 反向带日期改 `last_date`、带 `#NN` 改 `count`——反向即格式禁令的机器面);`shared/gates.md` §2.5 **+1 行(146 → 147)**,`docs/map/gates.md:18` 与 `docs/map/atlas-map.html` 计数同批同步;无新增校验器、无新台账、无生产代码。⑤ **边界/盲区**:**触发靠自述、可被逃**((b) 无机器判据——作者侧落点 = `shared/closeout.md` 步骤 3 定档附交叉检查结论、评审侧 = `shared/independent-review.md` §9;试点实测误报 0,不构成保证);「未参与挖弱点」语义不可机判,只靠证据链与评审回放;已归档族不追溯(重开仍走 DR1);Phase 2 = 第 3 份带「留出验证」章的 REVIEW 落盘即触发评估(成本 / 收益 / 失义),失义即退役。 |
|
|
632
|
+
| D157 | **E2E 多靶场 = 用例级 `靶场:` 字段 + `e2e.targets` 登记表(闭集)**(2026-10-27;现场驱动:某前端项目同一页的用例子集需要目标的**结构性前提**(另一文档实例),`seed:` 只播数据态、prepare 只能操作已有结构 ⇒ 契约唯一出口 `blocked` 等环境,长期卡死后诱发生成器外路由篡改——整页路由被改指另一实例,同分片其余用例的结构性前提被静默换掉) | ① **病灶**:`rings/e2e/reference.md` §5.4「靶场唯一」被读成「全项目永远恰好一个实例」,而页面分片全部用例共享页面表单格路由——结构性前提互斥的用例组无法共存,也没有「第二个目标」的合法声明形态。② **定案**:`stack-profile.yaml` 的 `e2e` 段新增可选子段 **`targets`**(`<名>: <路由>` 映射,闭集;名字 `[a-z0-9-]+`——进生成测试名 `__<名>` 后缀;路由形态同页面表 `路由` 列);分片 `atlas-case` 块新增可选标量字段 **`靶场:`**(值 = 登记名,缺省 = default 靶场)。生成器:用例 URL = `targets[<名>] ?? 页面表路由`(与 base query 合并口径同邻域);`守卫:` 缺省、`goto`/`refresh` 落点同源;**零步基线按解析后靶场 URL 分组**——default 组保持现名(`test_<页>__zero_step_baseline`,零变化承诺),具名组加 `__<名>` 后缀且只收该组用例探针(R9 每靶场各验各的初始态;blocked/skipped 本就不进探针)。校验双保险:`validate_stack_profile` 验 `targets` 形状;`validate_e2e_index` 新门「靶场 取值合法」(钉名必须在闭集内,整段未声明而钉名 ⇒ FAIL)+ `goto`/`守卫` 路径门放行**已登记靶场路由**(地址仍单源于 profile,自由路径仍禁)。**边界**:本字段只解决「同一页、同一批元素、不同目标实例」;断言另一页面元素 ⇒ 新建页 slug(testid 归属与双向集合门不受靶场影响)。**被否**:命令行 `--target` 旗标回归(E1 已退役;运行器临时换靶 = 地址脱单源);新页 slug 当隔离手段(对「同页同元素不同实例」过重——分裂 testid 命名空间与 AC 对齐);用例内联 URL(地址散落,违 §7 单源)。③ **零变化承诺**:未声明 `targets` 且无用例钉名 ⇒ 生成物逐字节一致(真实项目实测 sha 前后一致 + 出厂测试钉住)。④ **钉**:`tests/test_gen_e2e_scripts.py` 补具名靶场路由/基线分组/闭集中止三钉;`tests/test_validators.py` 补「靶场 取值合法」正反钉;`validate_stack_profile` 形状钉随 `test_tools`/`test_validators` 既有夹具口径。⑤ **边界/盲区**:多靶场共享同一运行器配置(baseURL/登录态/storageState)——具名靶场若跨域(不同 host),登录态预置按 default 靶场换取,跨 host 会话不在本批语义内(登记表内路由建议同源;跨 host 靶场出现时另案)。 |
|
|
633
|
+
| D158 | **`fill` 空串约定 = 值 `""` / `(空)` 清空输入(B173-1 对称面;现场实证 DSL 缺口)**(2026-10-27) | ① **病灶**:`value` 断言自 B173-1 起把 `""` / `(空)` 读作空串,但 `fill` 动作无对称口径——字面发射两个引号字符(现场:搜索框被填进 `""` 字面量 ⇒ 过滤恒不命中,「清空即恢复」类判据在 DSL 内无法表达)。② **定案**:`fill` 值 `""` / `(空)` ⇒ 发射 `fill("")`(Playwright fill 先清后写 = 清空);普通值逐字节不变。`type` 不变(文档明写空值报错,逐字键入无清空语义)。③ **被否**:新造 `clear` 动词(fill('') 即清空,动词增殖无增益);`press` 组合表达(多行才能表达一件事)。④ **钉**:`tests/test_gen_e2e_scripts.py` +2(清空发射钉 / 普通值零变化钉)。⑤ **边界**:仅 `fill`;`goto`/`select` 等其他带值动作不涉。 |
|
|
634
|
+
|
|
635
|
+
| D159 | **结构事实刷新钩子改挂 `after_archive`**(2026-10-28) | ① **病灶**:Trellis 的 `after_finish` 只清活动任务指针、不搬目录;归档(`task.py archive`,也是 `/trellis-finish-work` 的实际动作)只触发 `after_archive` ⇒ D38 的 `after_finish` 接线在「收尾即归档」的流程里几乎不触发,真正的收口时刻结构事实不刷新。② **定案**:`apply-wiring.py` 写 `after_archive`;存量 `after_finish` 里的同一条 refresh 命令**迁移**过去(自定义尾参 hook 原样保留);同一任务只挂一处,避免 finish→archive 双跑。③ **边界**:归档仍是显式动作(Trellis 不自动归档),本决策只改「哪个事件触发刷新」。④ **同步面**:`shared/gates.md` / `rings/structure/reference.md` / `apply/reference.md` / wiring README / `install.sh` 自检文案 / 两份 wiring 测试(含迁移用例)。 |
|
|
636
|
+
| D160 | **接线默认关闭 Trellis 自动提交**(2026-10-28) | ① **动机**:Trellis 的 `session_auto_commit` 默认 `true`,`task.py archive` / `add_session.py` 会**自动 stage + commit** journal 与任务归档;这绕过「提交由人 review」的协作口径,会在用户仓库里产生未经审阅的提交。② **定案**:`apply-wiring.py` 接线时默认写 `session_auto_commit: false`(出厂模板只有注释示例 ⇒ 原位替换,无键 ⇒ 末尾追加);**用户显式设置该键则尊重**(true/false 都不改),重跑幂等。③ **边界**:只影响 Trellis 的自动提交;atlas 自身的产物与 git 操作不变。④ **测试**:`tests/test_apply_wiring.py` 三例(默认写 false / 模板注释替换 / 显式 true 尊重)。 |
|
|
637
|
+
| D161 | **记录类留痕与审查报告共用 `reviews/` 命名空间**(2026-10-28;issue #44 / #43) | ① **病灶**:#44 —— `product/e2e/reviews/` 下验收走查 `<日期>-acceptance-<域>`、首跑分类 `<日期>-first-run`、执行器留档 `<日期>-verify` 只有 README / report.json,而「日期前缀最大」的报告定位(`atlas_check` 形态门与生成器前置门)把它们当审查报告 ⇒ 无 `review.json` 即 FAIL / invalid,门与脚本生成双双误红。#43 —— PRD 独立审查契约落 `product/prd/reviews/`,而 `validate_prd` 参数 0 禁止 `product/prd` 下任何子目录 ⇒ 按契约生成报告后 PRD 门必红。② **定案**:报告定位单源到 `gen_e2e_scripts.latest_review_report()`——保留名 `acceptance-* / first-run / verify` **且无 `review.json`** ⇒ 留痕,跳过;保留名带 `review.json` ⇒ 按报告校验(防改名绕过);非保留名缺 `review.json` ⇒ 仍候选,缺报告 = 未审,**不回退**旧报告。`atlas_check` 复用同一函数(同日基线消歧口径随之统一)。`validate_prd` 参数 0 把 `reviews/` 列为唯一保留子目录,其余子目录仍 FAIL;`closeout_derive` 审查报告路径 token 补 `<资产根>/prd/reviews/`。③ **被否**:留痕迁出 `reviews/` 父目录(改已发布路径 + 存量迁移);以 `review.json` 是否存在选报告(会把新报告漏写静默回退旧 PASS);PRD 报告迁 `product/reviews/`(与人工缺口清单角色混用)。④ **钉**:`tests/test_atlas_check.py` +2(留痕跳过 / 不回退 + 保留名带报告)、`tests/test_gen_e2e_scripts.py` +1(选择器 + status)、`test_validators.py` 扩 reviews 白名单、`test_closeout_derive.py` 断言路径元组。⑤ **同步面**:`shared/independent-review.md` §4、`rings/e2e/reference.md` §5.4/§7.1/§12.6、`rings/prd/reference.md` §2、`shared/layout.md`、`docs/concepts/layout.md`、`docs/design/prd-ring.md`、map 行数/锚。 |
|
|
638
|
+
| D162 | **校验器入口 `run()` 统一绝对 root**(2026-10-28;issue #45) | ① **病灶**:`load_layout()` 归一 `Path(root).absolute()`,`lay.*` 全为绝对路径;`validate_walkthrough.run()` 仍保留调用方传入的相对 root,`lay.prd_dir.relative_to(root)` 在「第 2 问=是 + 能解析域级 PRD」首次触发时抛 `ValueError`(atlas_check 以 `--root .` 调用)。既有测试全用绝对 `tmp_path`,该路径从未被咬到。② **定案**:`run()` 在布局解析成功后 `root = lay.root`,`tasks_root` / `check_task` / `relative_to` 全走同一绝对基。③ **被否**:只改 :229(留下同类混用面);只在 `main()` 归一(`run()` 直调仍暴露)。④ **钉**:`tests/test_validate_walkthrough.py` +1(`chdir(tmp_path)` + `run(Path("."))` + `main(["--root","."])`)。 |
|
|
639
|
+
| D163 | **AC 门适配器化与三形态适用性**(2026-10-29;`proposals/RFC-20261029-01` v2;评审 `accept with changes`) | ① **病灶**:`validate_ac_alignment.py` 把 pytest AST 扫描写进 validator(20+ 处框架名),违反 `shared/global-rules.md` §1;前三/四条判据不看 `backend_test` 与项目形态,纯前端有 PRD AC 时 advisory `PASS 0%`、enforce `FAIL`;非 Python 后端看不见测试;`parse_errors`/`scan_face` 只在维度子项出现,坏测试文件可整体 PASS;`apply-wiring.py` 把任意 `backend_test:` 当 Python 写 `pytest.ini`。② **定案**:新增 `adapters/ac/` 注册表 + pytest adapter;validator 只消费归一化记录(`marker_scope_unsupported`/`acs=marker∪doc`/`line>0`/`(file,line)` 唯一),不再出现 `ast`/`test_*.py`/框架名;无 backend/fullstack app ⇒ 整门 `N/A`,apps 缺失/未注册 extractor ⇒ `[UNVERIFIED]` WARN(enforce FAIL),零挂载 ⇒ `[ZERO-COVERAGE]` WARN(enforce FAIL),解析失败 ⇒ `[PARSE]`、扫描面非空 ⇒ `[SCAN]`;`apply-wiring` 改用 `python`/`framework: pytest`/`extractor: pytest` 判定;`atlas_check` AC 前置改 PRD 目录;`shared/stack-profile.md`/`gates` AC 行去 pytest 化;`templates/stack-profile.yaml` 通用化。③ **被否**:给 Java/TS 在 validator 里加分支(核心继续知道框架);Batch A 做外部 extractor `command`/JSON/parser(无真实消费者,投机);「无 backend kind 一律 N/A」(会吞掉无 apps + 无主 AC 的 FAIL);零覆盖保持 PASS(假绿)。④ **钉**:`tests/test_ac_adapters.py`(registry/pytest adapter/input_state/schema)、`tests/test_no_framework_names.py`(AST + `test_*.py` 语义钉)、`tests/test_validate_ac_alignment.py`(三形态矩阵/UNVERIFIED/ZERO-COVERAGE/PARSE/SCAN)、`test_apply_wiring`(非 Python 不写 pytest.ini)、`test_atlas_check`(PRD 目录前置)、`test_install_selfcheck`(adapters/ac 装配);变异 M-AC1..M-AC6。⑤ **边界**:外部 extractor 协议与 TS/Java adapter = Batch C;Java/Go 结构适配器与多 app/多模块 = Batch D;`shared/`/`rings/`/`templates/`/`patches/` 剩余框架名清理 = 必做 Batch B;部分覆盖(100 条 AC 挂 1 条仍 PASS)不在本决策范围。 |
|
|
640
|
+
| D164 | **TS/Vitest/Jest AC extractor(Batch C1)**(2026-10-29;`proposals/RFC-20261029-01` Batch C1;`adapters/ac/js.py`) | ① **病灶**:AC 门适配器化后,非 Python 后端仍报 `[UNVERIFIED]`;TS/JS 全栈项目的后端 AC 无可对账面。② **定案**:`adapters/ac/js.py` 用轻量词法扫描(不引 TS/JS parser)识别 `it/test/describe`、标题字符串、紧邻 JSDoc 与 `.skip/.todo/.fails/.skipIf`;AC = 标题 `[AC-...]` 或 JSDoc `@atlas-ac`,dims = `@atlas-dims`,waiver = 标题/`@atlas-waiver`;文件面 `*.test.*` / `*.spec.*` / `__tests__/**`,排除 `node_modules`、构建产物与 `product/e2e/`;注册 `vitest`/`jest` 到 `adapters/ac`。③ **被否**:引入 TS/JS parser 依赖(破坏零依赖);只认 JSDoc AC(标题更可见、报告可读);`test.each` 静态展开(v1 扫描面 WARN,不假装覆盖);Batch C1 同时做 JUnit/external protocol(拆到 C2)。④ **钉**:`tests/test_ac_adapters.py` 12 条(注册表 / pytest 记录 / JS 标题 AC / JSDoc dims / skip-waiver / describe 作用域 / 动态标题 / E2E 排除);`tests/test_validate_ac_alignment.py` 51 条(TS 端到端 PASS / 无主 AC / 无 waiver skip);变异 **M-JS1**(摘标题 AC 正则 ⇒ TS 覆盖钉红)/ **M-JS2**(摘 skip 识别 ⇒ 无 waiver skip 钉红),逐字节还原一致。⑤ **边界**:JUnit/外部 extractor 协议 = Batch C2;Java/Go 结构适配器 = Batch D;`test.each` / `skipIf` 表格不静态展开;部分覆盖仍不在本决策范围。 |
|
|
641
|
+
| D165 | **JUnit 5 内置 extractor + 外部 extractor 命令协议(Batch C2)**(2026-10-29;`proposals/RFC-20261029-01` Batch C2;`adapters/ac/junit.py`、`adapters/ac/command.py`) | ① **病灶**:Java/JUnit 后端仍报 `[UNVERIFIED]`;零散/私有测试栈也无法接入,除非在 validator 里加分支。② **定案**:新增 `adapters/ac/junit.py` 行级扫描(`@Test/@ParameterizedTest/@RepeatedTest/@TestFactory`、`@DisplayName`、`@Disabled/@Ignore`、`@Tag("atlas-dims-Cxx")`;不引 JavaParser/JVM);新增 `adapters/ac/command.py` 外部协议:`extractor_command` 为 argv 字符串,`shlex.split`、`shell=False`、120s 超时、10MB stdout 上限、`schema_version:1` 归一化 JSON,非 0/超时/非法 JSON ⇒ `[EXTRACTOR]` FAIL;`extractor_command` 与 `extractor`/`framework` 互斥,且无项目级 wire hook(不写测试配置)。③ **被否**:随包分发 JVM/JavaParser(破坏零依赖与分发体积);`shell=True`(注入面);外部命令失败时静默回退 pytest(假绿);用 `@Tag("atlas-dims:C01,C11")`(JUnit @Tag 不允许逗号);只做外部协议不提供内置 JUnit 兜底(Java 采用者仍无开箱支持)。④ **钉**:`tests/test_ac_adapters.py` 22 条(注册表 / pytest / JS / JUnit / 外部命令 valid·invalid JSON·exit·missing·schema);`tests/test_validate_ac_alignment.py` 57 条(JUnit 端到端 PASS / 无主 AC / 无 waiver skip;外部命令 PASS / 非法 JSON / 非 0);`tests/test_validators.py` 162 条(extractor_command 形态/互斥);`tests/test_apply_wiring.py` 26 条(外部命令不写测试配置);变异 **M-JUNIT1/M-JUNIT2/M-CMD1/M-CMD2** 均咬,逐字节还原一致。⑤ **边界**:Kotlin/TestNG 不在 v1;JavaParser 只作项目侧参考实现;`@TestFactory`/参数化占位符标 weak;外部命令无项目级 wire hook;Batch D 仍负责 Java/Go 结构适配器与多模块。 |
|
|
642
|
+
| D166 | **Java/Spring API + JUnit 测试清单结构适配器(Batch D1)**(2026-10-29;`adapters/api.py`、`adapters/tests.py`) | ① **病灶**:结构适配器只认 Python/TS;Java 后端的 `api` / `tests` 事实只能由 agent 手写,结构↔代码对账缺口大。② **定案**:`api.py` 新增 Spring 分支——`@RestController`/`@Controller` 类的 `@RequestMapping` 作 base path,方法级 `@GetMapping`/`@PostMapping`/`@PutMapping`/`@DeleteMapping`/`@PatchMapping`/`@RequestMapping` 组合;`@RequestMapping` 未声明动词记 `ANY`(不猜);类级多 base path × 方法路径笛卡尔展开。`tests.py` 新增 `.java` 盘点(`*Test.java` / `*Tests.java` / `*IT.java`)与 Maven/Gradle 配置探测(`pom.xml` / `build.gradle*`),命令 `mvn test` / `./gradlew test`(无 wrapper 用 `gradle test`);后缀族与配置族冲突仍置 null + WARN。③ **被否**:为注解识别引入 JavaParser(正则/行扫描足够,JVM 依赖留给外部 extractor);`@RequestMapping` 无动词按 GET/POST 猜(编事实);把全部 `.java` 当测试文件(`src/main` 排除);在 Batch D1 同时做 JPA models/Go/多模块 AC 门(拆 D2)。④ **钉**:`tests/test_adapter_api.py` 8 条(Spring 端点 / 多 base path / 非 controller 与注释忽略);`tests/test_tools.py` 29 条(Maven 命令 / Gradle wrapper / Java+pytest 冲突);变异 **M-JAVA1..M-JAVA4** 均咬,逐字节还原一致。⑤ **边界**:JPA `@Entity` models、Go 适配器、多 backend app / Maven-Gradle 多模块的 AC 门建模 = Batch D2;Kotlin/TestNG 不在 v1;`ANY` 为「动词未声明」的诚实值,不猜具体动词。 |
|
|
643
|
+
| D167 | **JPA 模型抽取 + 单 app 多模块测试盘点(Batch D2a)**(2026-10-29;`adapters/models.py`、`tests/test_validate_ac_alignment.py`) | ① **病灶**:Java 后端的 E 类(数据模型)仍只能 agent 手写;Maven/Gradle 多模块的测试盘点没有实测路径。② **定案**:`models.py` 新增 JPA 行级扫描——`@Entity` 类、`@Table(name=...)`、字段 `@Column`(name/nullable/unique/length)、`@Id`/`@GeneratedValue`、`@Enumerated` + `enum` 取值、`@ManyToOne`/`@OneToMany`/`@OneToOne`/`@ManyToMany` 关系;无注解字段也收,`@Transient`/`static` 跳过;表名无 `@Table` 时用类名(不猜命名策略)。多模块 = 单 app + `test_dir: .`,JUnit extractor 递归盘点各模块 `src/test/java`(测试钉两模块 2/2 覆盖)。③ **被否**:JavaParser(行扫描足够,JVM 依赖留给外部 extractor);JPA 继承/`@Embeddable` v1 收(字段来源跨类,先明示边界);表名按 snake_case 猜(编事实);多 backend app 门控塞进本批(D2b)。④ **钉**:`tests/test_adapter_models.py` 7 条(JPA 表/字段/约束/枚举/关系、非 entity 与 @Transient 忽略);`tests/test_validate_ac_alignment.py` 58 条(新增多模块 `test_dir: .` 2/2 覆盖钉);变异 **M-JPA1..M-JPA3** 均咬,逐字节还原一致。⑤ **边界**:JPA 继承/Embeddable、Go 适配器、多 backend app / 多模块 AC 门建模 = Batch D2b;Kotlin/TestNG 不在 v1。 |
|
|
644
|
+
| D168 | **多 backend app 的 AC 门改为统一 `test_dir` 全局对账(Batch D2b)**(2026-10-29;`validators/validate_ac_alignment.py`) | ① **病灶**:`backend_channel_reason` 对 `>1` 个 backend/fullstack app 直接返回 N/A,monorepo/多服务仓库的后端 AC 门被整门跳过。② **定案**:移除「多后端应用未建模」N/A;多个 backend app 共享一个 `test_dir`(建议 `.`),门按**全局 AC 覆盖**对账,不按 app 拆分;app 级归属仍由 structure 事实/agent 表达,`backend_test` schema 不变(单 `test_dir`)。③ **被否**:新增 `backend_test.apps[]` 逐 app 映射(schema/装配/校验面大,且当前消费者只需要全局 AC 覆盖);继续 N/A(把可用能力藏掉);按 app 自动推断测试目录(编事实)。④ **钉**:`tests/test_validate_ac_alignment.py` 59 条(多 app 同 `test_dir` PASS;多 app + `test_dir: .` 两应用 2/2 覆盖);变异 **M-MULTI1**(恢复多 app N/A ⇒ 钉红),逐字节还原一致。⑤ **边界**:app 级覆盖归属仍非机器门;JPA 继承/Embeddable、Go 适配器 = Batch D3;Kotlin/TestNG 不在 v1。 |
|
|
645
|
+
| D169 | **JPA 跨文件继承与 Embeddable 展开(Batch D3a)**(2026-10-29;`adapters/_jpa.py`、`adapters/models.py`) | ① **病灶**:JPA 字段只解析单个 `@Entity` 类体;`@MappedSuperclass` 继承字段与 `@Embeddable` 内嵌字段缺失,事实表少列。② **定案**:`_jpa.py` 建跨文件类图,`@Entity` 合并 `@MappedSuperclass` 链字段(只合并 mapped,不推断 JPA 继承策略),`@Embedded` 按类型展开为 `field.nested`;`@Table`/`@Column`/`@Enumerated`/关系注解沿用;无 `@Table` 表名 = 类名,不猜命名策略。③ **被否**:JavaParser(行扫描+类图足够);合并 `@Entity` 继承(JOINED/SINGLE_TABLE 语义不同,会编事实);snake_case 命名策略猜测;Embeddable 循环展开(v1 用 stack 阻断)。④ **钉**:`tests/test_adapter_models.py` 12 条(同文件与跨文件 mapped superclass、Embeddable 展开、普通父类不合并);变异 **M-JPA4/M-JPA5** 均咬,逐字节还原一致。⑤ **边界**:JPA 继承策略/`@ElementCollection`/Kotlin 不在 v1;`source_file` 记实体文件,字段来源类不单独归属。 |
|
|
646
|
+
| D170 | **Go 结构适配器(API / 测试清单 / 模型)(Batch D3b)**(2026-10-29;`adapters/api.py`、`adapters/tests.py`、`adapters/models.py`) | ① **病灶**:Go 后端的 `api` / `tests` / `models` 事实只能 agent 手写,结构↔代码对账缺口大。② **定案**:`api.py` 识别 gin/echo/chi/net/http 常见注册(`r.GET`、`http.HandleFunc("GET /x")`、`mux.HandleFunc("/x")`);无动词记 `ANY`,`_test.go` 不扫。`tests.py` 盘点 `*_test.go` + `go.mod`,命令 `go test ./...`;其它配置族冲突置 null + WARN。`models.py` 解析 Go struct 的 `gorm`/`json`/`db` tag(主键/自增/非空/唯一/size/column、`TableName()`),不猜表名。③ **被否**:Go AST/JavaParser(行级正则足够);按 `.Any` 猜具体动词(记 ANY);内置 Go AC extractor(v1 用 `extractor_command` 外部适配器,避免再铺一套测试标记 parser);Go 内嵌 struct/接口纳入 v1(边界明示)。④ **钉**:`tests/test_adapter_api.py` 9 条(Go 路由/注释忽略);`tests/test_tools.py` 31 条(Go 命令/配置冲突);`tests/test_adapter_models.py` 12 条(Go struct/tag/TableName/_test.go 排除);变异 **M-GO1/M-GO2/M-GO3** 均咬,逐字节还原一致。⑤ **边界**:Go AC 门需 `extractor_command`(无内置 Go AC extractor);Go 内嵌 struct/接口/多 backend app 逐 app 归属不在 v1;Kotlin/TestNG 不在 v1。 |
|
|
647
|
+
| D171 | **真实仓烟测修复:`.scratch` / MyBatis-Plus / NestJS-Midway**(2026-10-29;`adapters/_lib.py`、`adapters/_jpa.py`、`adapters/api.py`) | ① **病灶(真实仓复现)**:Java 仓 `.scratch/` 下的影子测试被当测试源(182→166 的重复事实);MyBatis-Plus `@TableName` 实体不被 JPA 解析器识别(0 张表),且 getter 的 `return id;` 被误当字段(`type=return`、重复行);NestJS/Midway `@Controller` + `@Get` 控制器产出 0 端点。② **定案**:共享忽略集补 `.scratch`/`target`/`.gradle`/`.idea`;`_jpa.py` 支持 `@TableName`/`@TableId`/`@TableField`、普通基类字段合并(仅 MyBatis entity)、跳过 `return/throw/...` 行;`api.py` 新增 `@Controller(prefix)` + `@Get/@Post/.../@All` 路由(无动词 `ANY`)。③ **被否**:把 `.scratch` 当合法测试源;继续只认 JPA(真实仓是 MyBatis-Plus);用裸 `@Get` 全仓扫描(只处理含 `@Controller` 的 TS/JS 文件)。④ **钉**:`tests/test_tools.py` 33 条(`.scratch` / `.atlas` 排除);`tests/test_adapter_models.py` 14 条(MyBatis 表/基类字段/getter 不产 `return` 行);`tests/test_adapter_api.py` 10 条(NestJS 控制器);变异 **M-REAL1..3** 均咬,逐字节还原。真仓烟测:nanwang AC 1/1 PASS、MyBatis 数据模型生成;cn-console AC 1/1 PASS、API 51 条、测试 20 条、`atlas check --fast` 无失败。⑤ **边界**:`.scratch` 是项目约定(不保证别名);MyBatis XML/注解 SQL 不产模型;NestJS DTO `class` 未纳入 TS 模型(真实 core 无 DTO 类,暂不铺)。 |
|
|
648
|
+
| D172 | **api 适配器运行时 schema 只认构造调用(AST)**(2026-10-29;issue #46) | ① **病灶**:`SCHEMA_MARKERS` 按原始文本匹配 `.py`,任何文件里的 `APIRouter(`/`FastAPI(` 字面量——适配器自身注释、自身正则源码、测试夹具字符串——都被当成框架事实 ⇒ 输出 `spec_entry="/openapi.json"` + `items=0` 的**假阳性指针**(仓内无描述文件,且 `refresh_structure`/`validate_structure` 会把它当合法指针接线)。issue 上报的另一半「`_lib.IGNORE_DIRS` 未排除 `.atlas/.trellis`」已随 D171 落地(带回归钉)。② **定案**:判定改走 AST——`ast.Call` 的 `func` 为 `Name`/`Attribute` 且名字 ∈ {`FastAPI`,`APIRouter`}(含 `from fastapi import FastAPI as App` 的别名展开);注释/文档字符串/字符串字面量天然不是 `Call`。文本预筛(不含标记名不解析)控开销;解析失败(py2/非法源码)WARN 跳过该文件,**不回退文本匹配**(回退即把缺陷带回;跑不起来的文件不构成运行时 schema 证据)。测试文件(`tests?/`、`test_*.py`、`*_test.py`、`conftest.py`)整类不作描述源判定——夹具里真起一个 app 也不是产品接口面。共享忽略集补 `.adapter-out`。③ **被否**:按路径豁免扫描器自身(挡不住把适配器源码拷进项目的形态);把源码枚举根限定 `apps[].path`(生产本就按 `--app` 调用,根级 `path: .` 的 app 触发面不变;§3.5「整项目」只约束**描述文件**探测);语法错误回退文本正则;`openapi_url=None` 等框架细节(登记边界不铺)。④ **钉**:`tests/test_adapter_api.py` +3(真构造调用仍产指针 / 别名构造 / 注释+字符串+夹具+适配器源码拷贝不产指针)+ `.adapter-out` 夹具入既有排除钉。⑤ **同步面**:`shared/stack-profile.md` §3.5、`adapters/api.py` 注释、map 行数锚。 |
|
|
606
649
|
---
|
|
607
650
|
|
|
608
651
|
## 13. 待实现事项
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Atlas — 践行 Project Harness 的全流程 AI 编程工作流
|
|
2
2
|
|
|
3
3
|
<p align="left">
|
|
4
|
-
<img src="https://img.shields.io/badge/version-0.2.0--beta.
|
|
4
|
+
<img src="https://img.shields.io/badge/version-0.2.0--beta.21-blue.svg" alt="Version" />
|
|
5
5
|
<img src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" alt="License" />
|
|
6
6
|
<img src="https://img.shields.io/badge/tests-passing-success.svg" alt="Tests" />
|
|
7
7
|
<a href="https://sunwenzhe-git.github.io/atlas/"><img src="https://img.shields.io/badge/docs-VitePress-green.svg" alt="Docs" /></a>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
- [三、快速开始(Quick Start)](#三快速开始quick-start)
|
|
32
32
|
- [1. 依赖准备](#1-依赖准备)
|
|
33
33
|
- [2. 全局安装 CLI 与项目初始化](#2-全局安装-cli-与项目初始化)
|
|
34
|
-
- [3. 核对技术栈画像 (`
|
|
34
|
+
- [3. 核对技术栈画像 (`stack-profile.yaml`)](#3-核对技术栈画像-stack-profileyaml)
|
|
35
35
|
- [4. 怎么快速使用?(大白话为主,确定性入口兜底)](#4-怎么快速使用大白话为主确定性入口兜底)
|
|
36
36
|
- [四、创新设计:E2E 用例评审控制台(人机协同终验)](#四创新设计e2e-用例评审控制台人机协同终验)
|
|
37
37
|
- [五、技术纵深:代码图谱(Code Graph)优化逆向抽取](#五技术纵深代码图谱code-graph优化逆向抽取)
|
|
@@ -69,7 +69,7 @@ NBER(美国国家经济研究局)2026 年工作论文 35275 追踪了超过
|
|
|
69
69
|
| :--- | :--- | :--- | :--- |
|
|
70
70
|
| **CPU / 算力核心** | **LLM(大语言模型)** | Claude 3.7 / GPT-5 / GLM-5 | 负责纯粹的逻辑推理与自回归计算(纯算力输入与输出) |
|
|
71
71
|
| **操作系统 / Runtime (OS)** | **Agent Harness** | Claude Code / Cursor / Pi Agent / OpenCode | **提供系统调用(Syscall)与运行环境**<br>• 文件读写、Bash 执行(工具调用)<br>• 内存与缓存管理(KV Cache 保温、上下文窗口截断)<br>• 多进程调度(Subagent 派生) |
|
|
72
|
-
| **工程框架 / 工业级脚手架** | **Project Harness** | **Atlas** / Trellis / OpenSpec | **定义项目物理秩序与全生命周期防线**<br>• 规范与应然资产基线(PRD / AC 账本)<br>•
|
|
72
|
+
| **工程框架 / 工业级脚手架** | **Project Harness** | **Atlas** / Trellis / OpenSpec | **定义项目物理秩序与全生命周期防线**<br>• 规范与应然资产基线(PRD / AC 账本)<br>• 依赖图谱影响面基准(advisory;后端栈条件项,纯前端栈近零收益)<br>• 机器物理阻断门禁(防止 AI 敷衍实现与伪造测试) |
|
|
73
73
|
|
|
74
74
|
### 3. 为什么说 Project Harness 是 AI 时代的 Spring Boot?
|
|
75
75
|
|
|
@@ -119,7 +119,7 @@ NBER(美国国家经济研究局)2026 年工作论文 35275 追踪了超过
|
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
### 1. 两层正交模型(项目级平面 🆚 需求级平面)
|
|
122
|
-
1. **项目级(应然资产层)**:定义“这个系统是什么、长什么样、怎么算做对”。包含**结构事实**(适配器 AST / 正则逆向抽取;图谱为可选文件枚举增强)、**项目级 PRD**(业务规则单源)、**E2E
|
|
122
|
+
1. **项目级(应然资产层)**:定义“这个系统是什么、长什么样、怎么算做对”。包含**结构事实**(适配器 AST / 正则逆向抽取;图谱为可选文件枚举增强)、**项目级 PRD**(业务规则单源)、**E2E 验收用例**(真实靶场可证伪用例),以及跨需求生效的**架构决策记录**(`<资产根>/adr/NNNN-<slug>.md`,默认 `product/adr/...`)。
|
|
123
123
|
2. **需求级(小需求执行层)**:定义“这次改动改什么、怎么写代码”。由内置的 **Trellis** 任务系统承担,Atlas 本身**不侵入业务代码实现**。
|
|
124
124
|
3. **`apply` 单向衔接**:需求在进入实现前,先由 Agent 唤起 `atlas-apply <需求ID>` 技能将需求增量合并入项目级应然资产,坚决杜绝“先写代码、事后补文档”的失控循环。
|
|
125
125
|
|
|
@@ -130,8 +130,10 @@ Atlas 的核心由 **三环(Rings)** 构筑成不可逾越的应然防线:
|
|
|
130
130
|
| 构件 | 角色 | 产物位置 | 核心机制 |
|
|
131
131
|
|---|---|---|---|
|
|
132
132
|
| **Ring 1: 结构基线** (`atlas-structure`) | **实然事实** | `.trellis/spec/structure/` | 适配器(AST / 正则)逆向提取代码事实(路由、数据模型、API、测试现状)。事实区由机器刷新,语义区由规范治理,双份维护(`current` / `legacy`)。 |
|
|
133
|
-
| **Ring 2: 项目级 PRD** (`atlas-prd`) | **业务应然** | `product/prd
|
|
134
|
-
| **Ring 3: 验收用例** (`atlas-e2e`) | **验证应然** | `product/e2e
|
|
133
|
+
| **Ring 2: 项目级 PRD** (`atlas-prd`) | **业务应然** | `<资产根>/prd/`(默认 `product/prd/`) | 业务叙事单一真相源(SSOT)。双向强制追踪链:`功能 ──► 业务规则 ──► 验收准则(AC) ──► E2E 用例`。不挂实现状态,保持应然纯粹性。 |
|
|
134
|
+
| **Ring 3: 验收用例** (`atlas-e2e`) | **验证应然** | `<资产根>/e2e/`(默认 `product/e2e/`) | 真实应用单段执行(Playwright)。总索引唯一入口,一例一片(页面分片),以 `data-testid` 为唯一硬锚,严禁将瞬时反馈(Toast)当终态。 |
|
|
135
|
+
|
|
136
|
+
> 原生结构适配器当前覆盖前端路由、Python/TS 模型与 API、测试清单,并已支持 Java Spring API、JPA 模型与 JUnit / Maven-Gradle 测试清单(单 app 多模块可用 `test_dir: .`);多 backend app 已支持统一 `test_dir` 全局 AC 对账;JPA/MyBatis-Plus 模型、Java Spring、NestJS/Midway 控制器与 Go API/测试/模型结构适配器也已支持(Go AC 门用 `extractor_command` 外部适配器)。
|
|
135
137
|
|
|
136
138
|
### 3. 唯一单向编排件(atlas-apply 会话技能)
|
|
137
139
|
|
|
@@ -163,8 +165,13 @@ cd your-project
|
|
|
163
165
|
|
|
164
166
|
# 一键初始化并装配 Atlas 工作流(自动探测平台、下发 Skills 与契约)
|
|
165
167
|
atlas init
|
|
168
|
+
|
|
169
|
+
# 如项目已有自己的文档规范(例如产品文档在 docs/),首次装配时指定资产根
|
|
170
|
+
atlas init --assets-root docs
|
|
166
171
|
```
|
|
167
172
|
|
|
173
|
+
首次 `atlas init` 会询问**项目级资产根**(直接回车 = 默认 `product/`),也可用 `--assets-root` / `--adr-dir` 非交互指定;选择写入项目根 `atlas.config.yaml`(git 跟踪)。`atlas update` 只读该配置,**不会重新询问或换根**;未配置的存量项目继续按 `product/` 工作。
|
|
174
|
+
|
|
168
175
|
装配命令将自动完成:
|
|
169
176
|
1. 将核心契约、校验器、模板整包镜像至目标项目 `.atlas/`;
|
|
170
177
|
2. 为你的 Agent 平台生成专用 Skills 瘦桩(`atlas-structure`, `atlas-prd`, `atlas-e2e`, `atlas-apply`, `atlas-grill`, `atlas-design-review`, `frontend-design` 等);
|
|
@@ -176,7 +183,7 @@ atlas init
|
|
|
176
183
|
> - **全局 CLI 自身升级**:运行 `atlas upgrade`(自动通过 npm 拉取最新全局包,支持 `--tag beta` 或 `--dry-run`)。
|
|
177
184
|
> - **项目内工程线束刷新**:在项目目录下运行 `atlas update`(安全刷新 `.atlas/` 资产与平台 Skills 瘦桩,版本戳对账更新,业务配置严格保护不覆盖)。
|
|
178
185
|
|
|
179
|
-
### 3. 核对技术栈画像 (`
|
|
186
|
+
### 3. 核对技术栈画像 (`stack-profile.yaml`,默认在 `product/`)
|
|
180
187
|
这份草稿**不用你手改**:首跑开户时 Agent 会带着你逐项核对(`origin` / `apps` / `role` / `adapters`)后落盘。出厂草稿含占位符,未替换真实字段前 `atlas check` 必有门禁 FAIL —— 这是 fail-closed 的预期行为,不是装坏了:
|
|
181
188
|
```yaml
|
|
182
189
|
product: MyAwesomeApp
|
|
@@ -227,7 +234,7 @@ Atlas 完全做到了**“日常全自动,低频全靠大白话”**:
|
|
|
227
234
|
```
|
|
228
235
|
无论哪种入口,Agent 都会逆向扫描现有代码拓扑,建立应然资产起点(不瞎猜老业务用例,只登记基线),并在开户前与你核对画像草稿、把占位符换成真实路径。
|
|
229
236
|
|
|
230
|
-
> ⚠️ **装配 ≠ 开户**:`atlas init` 只装配线束(`.atlas
|
|
237
|
+
> ⚠️ **装配 ≠ 开户**:`atlas init` 只装配线束(`.atlas/`、资产根骨架(默认 `product/`)、AGENTS.md 指针)。**开户产物是 `<资产根>/prd/` 与 `.trellis/spec/structure/` 的内容**——两者为空就是尚未开户。所以「已经装过 atlas」不代表已接入:此时若 Agent 回你「已接入」或反问你「是不是接过了」,直接敲 `/atlas-apply adopt` 即可(开户按产物存在与否断点续跑)。
|
|
231
238
|
|
|
232
239
|
> 完整流程、为什么存量页面不会把门禁刷红、后端项目的补充配置,见 [存量项目接入指南 (adopt)](https://sunwenzhe-git.github.io/atlas/guide/legacy-adoption)。
|
|
233
240
|
|
|
@@ -303,9 +310,9 @@ Atlas 随包自带了一个**纯 Python 标准库编写的本地轻量级可视
|
|
|
303
310
|
4. **单写者架构与提交落盘(Append-Only Ledger)**:
|
|
304
311
|
* 页面上的每一次执行和打勾先且仅保存在浏览器本地(`localStorage`,刷新不丢);
|
|
305
312
|
* 只有人类审视完毕、点击全局**「提交」**按钮时,才**一次性原子写入**:
|
|
306
|
-
* 向 `product/e2e/reviews/console/events.jsonl
|
|
313
|
+
* 向 `<资产根>/e2e/reviews/console/events.jsonl`(默认 `product/e2e/reviews/console/events.jsonl`)追加一条不可篡改的事件快照;
|
|
307
314
|
* 派生人读的 `ledger.md` 评审账本;
|
|
308
|
-
* 原子回写总索引 `product/e2e/e2e-index.md
|
|
315
|
+
* 原子回写总索引 `<资产根>/e2e/e2e-index.md`(默认 `product/e2e/e2e-index.md`)的状态列。
|
|
309
316
|
5. **评审指纹与陈旧度(代码一变,勾就作废)**:
|
|
310
317
|
* 每条提交事件与逐用例快照都绑定工作区指纹 `prov = {head_sha, scripts_hash, git_dirty, actor_type}`——**人审的到底是哪一版,账本自己说得出**;
|
|
311
318
|
* `scripts_hash` 只对源码脚本与数据准备钩子敏感,**对运行期产物免疫**(`test-results/`、trace、截图一律不入指纹),不会跑一轮就假漂移;
|
|
@@ -385,9 +392,11 @@ Atlas 坚持**变异证明(Mutation Proving / M 系列钉子测试)**原则
|
|
|
385
392
|
|
|
386
393
|
### 4. AC × 覆盖维度矩阵(防「表面用例」)
|
|
387
394
|
* **补的是哪个洞**:AC 反向覆盖门只能回答「这条 AC 有没有用例指向」,答不了「这条 AC 的异常流 / 边界 / 权限 / 会话恢复 / 幂等 / 状态转换有没有人验过」——于是 Agent 交上来 30 条看着齐整、真正会炸的一条都没有;
|
|
388
|
-
*
|
|
389
|
-
* **怎么写**:域级 PRD 验收标准表**可选第 4 列**
|
|
395
|
+
* **闭集**:`C01`–`C14`(正常流 / 异常流 / 边界值 / 权限 / 并发 / 重试 / 审计 / 会话恢复 / 数据一致性 / 依赖容错 / 幂等 / 输入校验 / 状态转换 / 安全);E2E 通道与后端通道共用同一闭集;
|
|
396
|
+
* **怎么写**:域级 PRD 验收标准表**可选第 4 列** `适用覆盖维度`(E2E 通道;支持 `C01, C02` 与 `C01-C03` 区间写法),用例分片写 `覆盖维度:` 列表声明这条用例打的是哪个失败面;**可选第 5 列** `后端适用覆盖维度`(后端通道,D153)声明后端义务;
|
|
390
397
|
* **怎么判**:已声明但未全覆盖 ⇒ **WARN** 逐条点名缺口;词表外的疑似写法(如 `C15`)⇒ WARN 出声,绝不静默吸收;**没写第 4 列的项目 ⇒ `N/A` 兜底**,不给任何存量项目凭空造红门;
|
|
398
|
+
* **后端通道(D153)**:后端测试用 extractor 定义的标记声明 AC / dims(当前内置 pytest、Vitest/Jest、JUnit 5,并支持 `extractor_command` 外部适配器;示例见 `docs/guide/backend-test.md`),`validate_ac_alignment.py` 对账;缺维度 / 仅弱证据 / 零测试 ⇒ WARN。第 4 列仍归 E2E,两列各查各的,**不做跨通道并集**;
|
|
399
|
+
* **三形态 × 能力矩阵**:纯前端 = testid + E2E 启用、AC 门 `N/A`;纯后端 = testid/E2E `N/A`、AC 门启用(需可用 extractor);全栈 = 两条通道都启用。没有 extractor 的栈会被报 `[UNVERIFIED]`(advisory WARN / enforce FAIL),不会伪装 PASS;原生 extractor 列表是能力矩阵,不是技术栈上限;
|
|
391
400
|
* **刻意不做的两件事**:不新建第 4 张手工对账表(撞 single-source,还会与 AC 唯一性定义门撞行),不强制每条 AC 覆盖 14 维(那只会催生几十条同形式的形式用例)。
|
|
392
401
|
|
|
393
402
|
---
|
|
@@ -428,7 +437,8 @@ Atlas 坚持**变异证明(Mutation Proving / M 系列钉子测试)**原则
|
|
|
428
437
|
* 需求实现后、归档收口前,由独立进程执行只读 diff 审查(Fresh Context 隔离;契约与转换器见 scripts/cli_review_convert.py);
|
|
429
438
|
* **应审 N / 实审 M 严格对账**:实测表明大模型审查大体积 diff 时会悄悄跳过文件;若实际评审文件数 `M < N`,无论结论如何一律报红拒绝,发现 `Critical` 缺陷一票否决。
|
|
430
439
|
3. **后端/全栈测试用例 ⇔ AC 跨语言对账门 (`validate_ac_alignment.py`)**:
|
|
431
|
-
*
|
|
440
|
+
* 校验后端与全栈测试用例中挂载的验收标记(标记语法由 `adapters/ac/` 的 extractor 解释)与 PRD 声明的 AC 集合严格对齐,防止实现测试漏测或假挂载。D153 起另对账测试记录的 `dims` 与 PRD 第 5 列 `后端适用覆盖维度`(维度缺口只 WARN,不随 `mode: enforce` 升 FAIL)。
|
|
441
|
+
* 三形态 / 未验证语义:无 backend/fullstack app ⇒ 整门 `N/A`;未注册 extractor / apps 缺失 ⇒ `[UNVERIFIED]`(advisory WARN / enforce FAIL);零挂载 ⇒ `[ZERO-COVERAGE]`、解析失败 ⇒ `[PARSE]`、扫描面非空 ⇒ `[SCAN]`,均不伪装 PASS。
|
|
432
442
|
4. **impact 预测对账门 (`impact_reconcile.py`)**:
|
|
433
443
|
* 在需求规划时预测的影响面集合,与收口时的真实 `git diff` 变更文件列表自动比对差集,持续校准模型的架构感知力。
|
|
434
444
|
5. **设计档案豁免 ⇔ 检测器双向对账 (`validate_design_exemptions.py`)**:
|
|
@@ -436,7 +446,7 @@ Atlas 坚持**变异证明(Mutation Proving / M 系列钉子测试)**原则
|
|
|
436
446
|
6. **装配版本完整性门 (`.atlas/VERSION`)**:
|
|
437
447
|
* 目标项目中的装配态与 Atlas 源包内容哈希实时对账,源码更新未重新同步时明确报警。
|
|
438
448
|
7. **架构决策记录对账门 (`validate_adr.py`)**:
|
|
439
|
-
* 项目级长效架构决策唯一落 `product/adr/NNNN-<slug>.md
|
|
449
|
+
* 项目级长效架构决策唯一落 `<资产根>/adr/NNNN-<slug>.md`(默认 `product/adr/NNNN-<slug>.md`);校验编号唯一、状态枚举合法、演进链闭环(`superseded` 必指有效后继)、核心章节(Context / Decision / Consequences)完整,杜绝决策散落多处或私自改写。
|
|
440
450
|
8. **独立审查报告形态与处置门 (`validate_review_report.py` + 单源内核 `_support/review_policy.py`)**:
|
|
441
451
|
* `review.json` v2 契约:finding 级 `fingerprint`(剥掉行号的稳定哈希,同一问题跨轮不得重复刷分)/ `pre_existing`(存量代码缺陷降为 WARN 下限、不阻断本次收口,但仍可见)/ `disposition`(`fixed`、`false_positive` 必须带签字人与取证才生效)/ `verification`;
|
|
442
452
|
* **轮次收敛**:第 4 轮起必须登记 `escalation`(升级到人/架构复议),杜绝「Agent 作者 ⇔ Agent 评审」互相忙到天亮的死循环;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""共享内核 —— AC 覆盖维度解析(E2E 第 4 列 / 后端列单源)。
|
|
3
|
+
|
|
4
|
+
D130 的维度 token 口径从 `validate_e2e_index.py` 原样抽出(2026-10-26,D153):
|
|
5
|
+
* C01-C14 闭集;
|
|
6
|
+
* 区间 `C01-C03` 展开;
|
|
7
|
+
* 中文邻接 `C01正常流` 命中(`\\b` 对 CJK 失效);
|
|
8
|
+
* 疑似写法 `C99` 由 `looks` 出声。
|
|
9
|
+
|
|
10
|
+
消费方:
|
|
11
|
+
* `validate_e2e_index.py`:`parse_dims_cell`(纯抽取,行为逐字节一致);
|
|
12
|
+
* `validate_ac_alignment.py`:`parse_dims_cell`(后端列)+ `parse_dim_token` / `parse_dims_list`(结构化 dims)。
|
|
13
|
+
|
|
14
|
+
**不负责** PRD 表定位 / E2E 索引解析;`iter_md_tables` 只是通用 Markdown 表切分,
|
|
15
|
+
供后端列解析复用,避免第四份表解析器口径漂移(parity 钉见 `tests/test_shared_kernel.py`)。
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
DIM_IDS = tuple(f"C{i:02d}" for i in range(1, 15))
|
|
22
|
+
|
|
23
|
+
_DIM_TOKEN = r"C(?:0[1-9]|1[0-4])"
|
|
24
|
+
_DIM_RANGE_RE = re.compile(
|
|
25
|
+
rf"(?<![0-9A-Za-z])({_DIM_TOKEN})\s*[-~至]\s*({_DIM_TOKEN})(?![0-9])"
|
|
26
|
+
)
|
|
27
|
+
_DIM_CODE_RE = re.compile(rf"(?<![0-9A-Za-z]){_DIM_TOKEN}(?![0-9])")
|
|
28
|
+
_DIM_LOOKS_RE = re.compile(r"[Cc]\d{1,2}")
|
|
29
|
+
_DIM_EXACT_RE = re.compile(rf"^{_DIM_TOKEN}$")
|
|
30
|
+
# 备注/错位列表头扫描用:排除 AC ID 里的 `AC-C01-...`(防把 AC 编号当维度声明)。
|
|
31
|
+
_DIM_CLAIM_RE = re.compile(r"(?<!AC-)(?<![0-9A-Za-z])[Cc](\d{1,2})(?![0-9])")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_dims_cell(raw) -> tuple[set[str], bool]:
|
|
35
|
+
"""维度单元格文本 → (命中维度集合, 是否含疑似维度写法)。区间展开、中文邻接可命中。"""
|
|
36
|
+
text = str(raw or "")
|
|
37
|
+
dims: set[str] = set()
|
|
38
|
+
for m in _DIM_RANGE_RE.finditer(text):
|
|
39
|
+
lo, hi = int(m.group(1)[1:]), int(m.group(2)[1:])
|
|
40
|
+
if lo > hi:
|
|
41
|
+
lo, hi = hi, lo
|
|
42
|
+
dims |= {f"C{i:02d}" for i in range(lo, hi + 1)}
|
|
43
|
+
dims |= set(_DIM_CODE_RE.findall(text))
|
|
44
|
+
return dims, bool(_DIM_LOOKS_RE.search(text))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_dim_token(raw) -> str | None:
|
|
48
|
+
"""精确 token:仅接受 C01-C14;`C01正常流` / `C01C02` / `C99` 一律 None。"""
|
|
49
|
+
text = str(raw or "").strip().strip("`").strip()
|
|
50
|
+
return text if _DIM_EXACT_RE.fullmatch(text) else None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_dims_list(value) -> tuple[set[str], list[str], bool]:
|
|
54
|
+
"""结构化 `dims=` → (命中集合, 坏 token 列表, 形态是否受支持)。
|
|
55
|
+
|
|
56
|
+
支持 list / tuple / 逗号(中英文)分隔字符串;元素必须是 C01-C14 精确 token。
|
|
57
|
+
set / 变量 / 非字符串元素 ⇒ shape_ok=False(调用方 WARN「形态不支持」)。
|
|
58
|
+
"""
|
|
59
|
+
if isinstance(value, str):
|
|
60
|
+
items = [x for x in re.split(r"[,,\s]+", value.strip()) if x]
|
|
61
|
+
elif isinstance(value, (list, tuple)):
|
|
62
|
+
items = list(value)
|
|
63
|
+
else:
|
|
64
|
+
return set(), [], False
|
|
65
|
+
|
|
66
|
+
dims: set[str] = set()
|
|
67
|
+
bad: list[str] = []
|
|
68
|
+
for item in items:
|
|
69
|
+
if not isinstance(item, str):
|
|
70
|
+
return set(), [], False
|
|
71
|
+
token = parse_dim_token(item)
|
|
72
|
+
if token:
|
|
73
|
+
dims.add(token)
|
|
74
|
+
elif item.strip():
|
|
75
|
+
bad.append(item)
|
|
76
|
+
return dims, bad, True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def split_md_row(line: str) -> list[str]:
|
|
80
|
+
"""单行 Markdown 表 → 单元格(去首尾 `|`,逐格 strip)。"""
|
|
81
|
+
line = line.strip()
|
|
82
|
+
if line.startswith("|"):
|
|
83
|
+
line = line[1:]
|
|
84
|
+
if line.endswith("|"):
|
|
85
|
+
line = line[:-1]
|
|
86
|
+
return [c.strip() for c in line.split("|")]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def iter_md_tables(text: str):
|
|
90
|
+
"""逐张产出 (header, rows);分隔行剔除。口径与 `validate_e2e_index.tables()` 一致。"""
|
|
91
|
+
block: list[list[str]] = []
|
|
92
|
+
for line in text.splitlines():
|
|
93
|
+
if line.strip().startswith("|"):
|
|
94
|
+
block.append(split_md_row(line))
|
|
95
|
+
elif block:
|
|
96
|
+
yield block[0], [r for r in block[1:] if not all(set(c) <= set("-: ") for c in r)]
|
|
97
|
+
block = []
|
|
98
|
+
if block:
|
|
99
|
+
yield block[0], [r for r in block[1:] if not all(set(c) <= set("-: ") for c in r)]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def strip_md_cell(raw) -> str:
|
|
103
|
+
"""表头 / AC 单元格去空白与反引号。"""
|
|
104
|
+
return str(raw or "").strip().strip("`").strip()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def has_dim_claim(raw) -> bool:
|
|
108
|
+
"""单元格是否含维度形态(备注/错位表头的疑似扫描;排除 AC ID 的 `AC-` 前缀)。"""
|
|
109
|
+
return bool(_DIM_CLAIM_RE.search(str(raw or "")))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def unknown_dim_tokens(raw) -> list[str]:
|
|
113
|
+
"""从散文单元格里找出**非 C01-C14** 的维度形态 token(如 `C99` / `C15`)。"""
|
|
114
|
+
out: list[str] = []
|
|
115
|
+
for m in _DIM_CLAIM_RE.finditer(str(raw or "")):
|
|
116
|
+
token = f"C{int(m.group(1)):02d}"
|
|
117
|
+
if token not in DIM_IDS and token not in out:
|
|
118
|
+
out.append(token)
|
|
119
|
+
return out
|
|
120
|
+
|