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/apply/reference.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 本环为 atlas 独立 skill `atlas-apply` 的执行契约,位于 `.atlas/apply/reference.md`。
|
|
4
4
|
> 执行前先读共享契约:`.atlas/shared/global-rules.md`、`stack-profile.md`、`single-source.md`、`layout.md`。
|
|
5
|
+
> 路径约定:本文 `product/` 均为**默认资产根**示例;实际按项目根 `atlas.config.yaml` 的 `layout.assets_root` 解析(见 `shared/layout.md` §1.1)。
|
|
5
6
|
> 本环**跨四环**,是 atlas 里唯一的编排件:它只做「按需求跨环增量」与「按序全量」两件事,环内逻辑一律引用各环自己的契约,不在此复制正文。
|
|
6
7
|
|
|
7
8
|
## 0. 一句话
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
|
|
53
54
|
## 3. 参与环与顺序
|
|
54
55
|
|
|
55
|
-
`apply <需求ID>` 按 **`prd → e2e`** 顺序处理;`structure` **不参与**该增量(它是 apply 的**输入**,不是产出——实然事实在实现后由 `
|
|
56
|
+
`apply <需求ID>` 按 **`prd → e2e`** 顺序处理;`structure` **不参与**该增量(它是 apply 的**输入**,不是产出——实然事实在实现后由 `after_archive` hook 与 `trellis-update-spec` 维护)。
|
|
56
57
|
|
|
57
58
|
**唯一例外(有界)**:若本次 PRD 定稿**改变了域清单**(合并 / 拆分 / 改名),在 `prd` 之后追加一次 structure **re-key**(按 `rings/structure/reference.md` §2.2:按最终域重排 `.trellis/spec/structure/{current,legacy}/<domain>/` 目录并回写 `_meta.json.domains` / `tour.md`,**不重读代码**)。除该情形外,apply 不写结构事实。
|
|
58
59
|
|
|
@@ -70,13 +71,14 @@
|
|
|
70
71
|
2. **这次是否需要人来确认应然 UI / 流程**?**是** ⇒ `prd` 六件套(用户流程 / 状态机 / 字段 / 文案 / 异常)落细 **+ 派生「用户旅程走查」并取得用户确认(`rings/prd/reference.md` §4·八;确认前不得进 `e2e` 环)**;应然 UI 的 testid 应然随用例分片产出(E1:原型环已移除)。
|
|
71
72
|
3. **这次是否需要可重复的验收**(超出 lint / test / typecheck 的能力)?**是** ⇒ E2E 环参与。
|
|
72
73
|
|
|
73
|
-
- **三问全否 ⇒ 只走 `structure` + Trellis**:atlas 三环记 `不适用`,报告里必须留一行 `本需求判定:纯加法 / 不启用三环` + 依据。**这是一条合法路径,不是绕过工作流**——结构事实仍由 `
|
|
74
|
+
- **三问全否 ⇒ 只走 `structure` + Trellis**:atlas 三环记 `不适用`,报告里必须留一行 `本需求判定:纯加法 / 不启用三环` + 依据。**这是一条合法路径,不是绕过工作流**——结构事实仍由 `after_archive` hook 保鲜,实现任务照旧拿到路径注入的事实。
|
|
74
75
|
- **`prd` 不再恒参与**(原口径:任何需求至少在域级 PRD / 追溯总表留一条)。它在**资产为空的旧项目**上等于「第一个小需求就要写全套项目级 PRD」——成本炸弹,而且与 §2 输入表(要求资产已存在)自相矛盾。改为按上面的门判定;若判定「本需求不改任何应然资产」(纯内部重构且不影响产品定义),apply 报告 `no-op` 并写明依据,**不**强改资产。
|
|
75
76
|
- **两条报告口径**并列**,可同段共存**(2026-09-27 补,`3509 §B91`):① 三问全否 ⇒ 写 `本需求判定:纯加法 / 不启用三环` + 依据;② 本需求不改任何应然资产 ⇒ 写 `no-op` + 依据。一个需求可以**两条同时命中**(如「新增一个与产品无关的通用工具函数」:既三问全否,又不碰任何应然资产)——此种情形**两行都写**,不择一、也无优先序。
|
|
76
77
|
- **门是语义判断,机器判不了**(与 `§8 末注` 同源):所以要求把**判定 + 依据**写进 `atlas-apply.md`,让复核者一眼看出它有没有偷懒。
|
|
77
78
|
- **E2E 首跑取证兜底(2026-10-16,`RFC-20261015-02` / issue #11)**:第 3 问判「是」且 `e2e.app_login` 仍未声明 ⇒ 向用户**追问一次**(`real` 四值 / `mode: none` / `fabricated` + `storage_key`,三选一必须显式),落 `product/stack-profile.yaml` 后再进 e2e 环。追问是 UX 加速器,不是判据——机器判据是生成器 `not_ready`(升档 = 下一次真跑准备日,`shared/gates.md`「E2E 真跑」行);adopt 开户已问过(同批「靶场接入取证」)的项目此处自然不触发。
|
|
78
79
|
- **环间人工门(E1 起;2026-09-29 重议 = `3507 AQ`,B124 结论)**:两道人侧门——① **走查单确认门(实现前,硬)**:改变应然 UI 的需求,域级 PRD 的「用户旅程走查」节须用户显式确认(节首 `已确认:<日期>`;`rings/prd/reference.md` §4·八,确认前对照 `product/design.md`(存在时)视觉方向,`3507 BE`),确认前 **`apply` 不得进 `e2e` 环**;② **验收走查门(收口前)**:该域 E2E 在真实应用全 green 后,用户按域级 PRD 功能清单逐条在真实应用上过一遍(可用 + 符合已确认走查单),确认后才算域收口;不符 ⇒ 差异登记且域不收口;真实应用不可用 ⇒ 与 E2E 同为 `blocked` 显式带原因(`rings/e2e/reference.md` §5.4 末)。`e2e` 用例另走独立审查门(`rings/e2e/reference.md` §12)+ 首跑分类协议(同文件 §5.4)。原型增量人工 review 门已随环退役(`3508 §92`)。
|
|
79
|
-
-
|
|
80
|
+
- **走查单确认门的机器面(2026-10-23,`RFC-20261022-01` M1)**:`validators/validate_walkthrough.py` 读本报告(`atlas-apply.md`)第 2 问**已写下**的判定,与 `product/prd/<domain>-prd.md` 的 `> 已确认:YYYY-MM-DD` 行对账。因此报告里第 2 问的答句必须写成「是 / 否」开头;受影响域以报告内对 `product/prd/<domain>-prd.md` 的路径引用为准(解析不到 ⇒ WARN 要求人工核对)。本器只判「写下的『是』与确认行的对账」,不判三问语义对错(那仍归人)。
|
|
81
|
+
- 结构事实的**实现后**回写不属于 apply:由 `after_archive` hook(`refresh_structure.py`,事实区)与 Finish 的 `trellis-update-spec`(语义区)负责(D38)。
|
|
80
82
|
|
|
81
83
|
`all` 模式的顺序固定为 **`structure → prd → e2e`**(含 structure 全量)。
|
|
82
84
|
|
|
@@ -194,6 +196,7 @@ apply 只对**本次受影响集合**落笔,**不整文件覆盖**任何项目
|
|
|
194
196
|
|
|
195
197
|
- **单写者表面(硬)**:每个共享文件在任一时刻**只允许一个在办任务**持有写权。
|
|
196
198
|
- **声明与分派**:并发前,每个任务必须**声明自己要写哪些项目级文件**;由用户 / 编排者按 `one writer per file` 分派;冲突时**停下报告**,不得抢占(实测第三次才守住,`3508 §55 一`)。
|
|
199
|
+
- **claim 钩子(2026-10-23,`RFC-20261022-01` M3)**:共享文件动手前 `python3 .atlas/scripts/write_claim.py take --file <路径> --task <需求ID>`,收口时 `release --file <路径>`。每 claim 一个原子文件(`.atlas/runs/write-claims/<hash>.json`,`O_CREAT|O_EXCL`)⇒ 并发 take 同一文件时后者**直接退出非零并指名在办任务**;未释放的 claim 由 `atlas_check` 的 `写面残留` 行 WARN 点名。**降级路径**:旧装配无此命令 ⇒ 纪律照旧(本钩子的主语义是「有命令则机器判」,安全性不押在降级上)。claims 是运行态,`.atlas/` 不入库 ⇒ 不进 git 历史;`validate_e2e_index` 的事后兜底保留不撤。
|
|
197
200
|
- **降串行度的建议(非硬)**:索引类表面可**按用例 ID 分区**(或拆成每页一份的反向索引);但反向视图现在是**生成**的(`single-source` §2),所以首选是「一次只让一个任务改主表」。
|
|
198
201
|
- **不属本节**:需求级文件(`.trellis/tasks/**`)天然一任务一份,无需仲裁。
|
|
199
202
|
|
package/bin/atlas.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const { spawnSync } = require('child_process');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const readline = require('readline');
|
|
5
6
|
|
|
6
7
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
7
8
|
const args = process.argv.slice(2);
|
|
@@ -27,6 +28,7 @@ Atlas — 践行 Project Harness 的全流程 AI 编程工作流治理引擎
|
|
|
27
28
|
示例:
|
|
28
29
|
atlas init # 在当前项目初始化装配
|
|
29
30
|
atlas init --target /path # 在指定项目初始化装配
|
|
31
|
+
atlas init --assets-root docs # 首次装配指定项目级资产根(默认 product)
|
|
30
32
|
atlas update # 刷新当前项目的工程线束与资产
|
|
31
33
|
atlas update --dry-run # 预览项目更新改动
|
|
32
34
|
atlas upgrade # 升级全局 CLI 至最新发布版
|
|
@@ -41,7 +43,7 @@ switch (command) {
|
|
|
41
43
|
case 'init': {
|
|
42
44
|
const installScript = path.join(PKG_ROOT, 'install.sh');
|
|
43
45
|
const passthroughArgs = args.slice(1);
|
|
44
|
-
|
|
46
|
+
|
|
45
47
|
// 如果没有指定 --target,默认使用当前工作目录
|
|
46
48
|
let hasTarget = false;
|
|
47
49
|
let targetDir = process.cwd();
|
|
@@ -52,36 +54,72 @@ switch (command) {
|
|
|
52
54
|
break;
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
|
|
58
|
+
const hasAssetsRoot = passthroughArgs.includes('--assets-root');
|
|
59
|
+
const hasConfig = fs.existsSync(path.join(targetDir, 'atlas.config.yaml'));
|
|
60
|
+
const hasAtlas = fs.existsSync(path.join(targetDir, '.atlas'));
|
|
61
|
+
const shouldPrompt = !hasAssetsRoot && !hasConfig && !hasAtlas
|
|
62
|
+
&& process.stdin.isTTY && process.stdout.isTTY;
|
|
63
|
+
|
|
64
|
+
function runInstall(extraArgs) {
|
|
65
|
+
const baseArgs = hasTarget ? passthroughArgs : ['--target', targetDir, ...passthroughArgs];
|
|
66
|
+
const finalArgs = [...baseArgs, ...extraArgs];
|
|
67
|
+
|
|
68
|
+
// Trellis 环境检测与自动初始化引导
|
|
69
|
+
const trellisDir = path.join(targetDir, '.trellis');
|
|
70
|
+
if (!fs.existsSync(trellisDir)) {
|
|
71
|
+
const hasTrellis = spawnSync('which', ['trellis']).status === 0;
|
|
72
|
+
if (hasTrellis) {
|
|
73
|
+
console.log('\n[Atlas] 检测到目标项目尚未初始化 Trellis (.trellis/ 缺失)。');
|
|
74
|
+
console.log('[Atlas] 正在自动运行 trellis init 初始化状态机骨架...');
|
|
75
|
+
const initRes = spawnSync('trellis', ['init'], {
|
|
76
|
+
cwd: targetDir,
|
|
77
|
+
stdio: 'inherit',
|
|
78
|
+
env: process.env,
|
|
79
|
+
});
|
|
80
|
+
if (initRes.status !== 0) {
|
|
81
|
+
console.warn('[Atlas] ⚠️ 警告: trellis init 执行异常,继续执行 Atlas 装配...');
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
console.warn('\n[Atlas] ⚠️ 提示: 未检测到 Trellis CLI。');
|
|
85
|
+
console.warn('[Atlas] Atlas 需求级执行依赖 Trellis,建议稍后安装:');
|
|
86
|
+
console.warn(' npm install -g @mindfoldhq/trellis\n');
|
|
71
87
|
}
|
|
72
|
-
} else {
|
|
73
|
-
console.warn('\n[Atlas] ⚠️ 提示: 未检测到 Trellis CLI。');
|
|
74
|
-
console.warn('[Atlas] Atlas 需求级执行依赖 Trellis,建议稍后安装:');
|
|
75
|
-
console.warn(' npm install -g @mindfoldhq/trellis\n');
|
|
76
88
|
}
|
|
89
|
+
|
|
90
|
+
const res = spawnSync('bash', [installScript, ...finalArgs], {
|
|
91
|
+
stdio: 'inherit',
|
|
92
|
+
env: process.env,
|
|
93
|
+
});
|
|
94
|
+
process.exit(res.status || 0);
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
97
|
+
if (shouldPrompt) {
|
|
98
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
99
|
+
rl.question(
|
|
100
|
+
'\nAtlas 项目级资产根目录(PRD / E2E / ADR 等)[product]: ',
|
|
101
|
+
(answer) => {
|
|
102
|
+
rl.close();
|
|
103
|
+
const value = (answer || '').trim() || 'product';
|
|
104
|
+
const validation = spawnSync(
|
|
105
|
+
'python3',
|
|
106
|
+
[path.join(PKG_ROOT, '_support', 'layout.py'),
|
|
107
|
+
'--root', targetDir, '--validate-value', 'assets_root', value],
|
|
108
|
+
{ encoding: 'utf8' },
|
|
109
|
+
);
|
|
110
|
+
if (validation.status !== 0) {
|
|
111
|
+
console.error('[Atlas] 无效资产根:' + (validation.stderr || '').trim());
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
console.log('[Atlas] 资产根 = ' + value
|
|
115
|
+
+ '(Atlas 资产会放在此目录;已有文件不会被覆盖)');
|
|
116
|
+
runInstall(['--assets-root', value]);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
83
121
|
|
|
84
|
-
|
|
122
|
+
runInstall([]);
|
|
85
123
|
break;
|
|
86
124
|
}
|
|
87
125
|
|
|
@@ -107,7 +145,7 @@ Atlas update — 安全刷新当前项目中的 .atlas/ 工程线束与平台 Sk
|
|
|
107
145
|
说明:
|
|
108
146
|
atlas update 会安全刷新当前项目中的 .atlas/ 契约、校验器、模板、脚本以及各平台
|
|
109
147
|
Skills 瘦桩,并重新对账生成 .atlas/VERSION 装配版本戳。
|
|
110
|
-
|
|
148
|
+
项目原有的业务资产(默认资产根下的 stack-profile.yaml、e2e/cases/ 等)将严格保留。
|
|
111
149
|
`);
|
|
112
150
|
process.exit(0);
|
|
113
151
|
}
|
|
@@ -300,7 +338,7 @@ Atlas upgrade — 自动拉取 npm 最新全局包升级全局 Atlas CLI
|
|
|
300
338
|
1. 在 Agent 会话中直接输入大白话,例如:
|
|
301
339
|
- "帮我把这个项目接入 Atlas 工作流" (意图 = adopt 存量开户)
|
|
302
340
|
- "跑一下三环全量构建,建立应然基线" (意图 = all 全量构建)
|
|
303
|
-
注意「装配 ≠
|
|
341
|
+
注意「装配 ≠ 开户」:开户产物是资产根/prd/ 与 .trellis/spec/structure/ 的内容。
|
|
304
342
|
若 Agent 因 .atlas/ 已存在而回你「已接入」,改用下面第 2 种(确定性入口)。
|
|
305
343
|
2. 或在支持 Slash Command 的平台中唤起技能:
|
|
306
344
|
/atlas-apply ${sub}
|
package/extensions/pi.ts
CHANGED
|
@@ -16,15 +16,16 @@ import type {
|
|
|
16
16
|
} from "@earendil-works/pi-coding-agent";
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
19
20
|
|
|
20
21
|
const EXTENSION_NAME = "atlasharness";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* 开户态(2026-10-15 补):**装配 ≠ 开户**。
|
|
24
25
|
*
|
|
25
|
-
* `atlas init` 只装配线束(`.atlas/` / `product
|
|
26
|
+
* `atlas init` 只装配线束(`.atlas/` / 资产根骨架(默认 `product/`) / AGENTS.md 指针 / Trellis 接线);
|
|
26
27
|
* 「开户」另有产物,且只有该轮真产出才算数:
|
|
27
|
-
* *
|
|
28
|
+
* * `<资产根>/prd/prd.md` —— 全局 PRD(`adopt` 最小必填集必产;默认 `product/prd/prd.md`)
|
|
28
29
|
* * `.trellis/spec/structure/_meta.json` —— 域清单(`structure` 全量的机器视图)
|
|
29
30
|
* 骨架期这两个文件都不存在(目录里只有 `.gitkeep`)⇒ **不能拿「目录非空」当信号**。
|
|
30
31
|
* 「半开通」(只有其一)是常态:开户按产物存在与否**断点续跑**(`apply/reference.md` §1)。
|
|
@@ -43,15 +44,39 @@ interface AtlasProjectStatus {
|
|
|
43
44
|
hasStackProfile: boolean;
|
|
44
45
|
onboarding: Onboarding;
|
|
45
46
|
version?: string;
|
|
47
|
+
stackProfileRel: string;
|
|
48
|
+
prdRel: string;
|
|
49
|
+
layoutNote?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function layoutValue(cwd: string, key: string, absolute: boolean): string | null {
|
|
53
|
+
const script = join(cwd, ".atlas", "_support", "layout.py");
|
|
54
|
+
if (!existsSync(script)) return null;
|
|
55
|
+
const flag = absolute ? "--print-abs" : "--print";
|
|
56
|
+
const res = spawnSync("python3", [script, "--root", cwd, flag, key], { encoding: "utf8" });
|
|
57
|
+
if (res.status !== 0) return null;
|
|
58
|
+
const out = (res.stdout || "").trim();
|
|
59
|
+
return out || null;
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
function detectAtlasStatus(cwd: string): AtlasProjectStatus {
|
|
49
63
|
const hasAtlasDir = existsSync(join(cwd, ".atlas"));
|
|
50
|
-
const
|
|
64
|
+
const hasConfig = existsSync(join(cwd, "atlas.config.yaml"));
|
|
65
|
+
const stackProfileAbs = layoutValue(cwd, "stack_profile", true)
|
|
66
|
+
?? join(cwd, "product", "stack-profile.yaml");
|
|
67
|
+
const stackProfileRel = layoutValue(cwd, "stack_profile", false)
|
|
68
|
+
?? "product/stack-profile.yaml";
|
|
69
|
+
const prdAbs = layoutValue(cwd, "prd", true) ?? join(cwd, "product", "prd");
|
|
70
|
+
const prdRel = layoutValue(cwd, "prd", false) ?? "product/prd";
|
|
71
|
+
const hasStackProfile = existsSync(stackProfileAbs);
|
|
51
72
|
const hasTrellis = existsSync(join(cwd, ".trellis"));
|
|
73
|
+
const layoutReady = !hasConfig || layoutValue(cwd, "assets_root", false) !== null;
|
|
74
|
+
const layoutNote = hasConfig && !layoutReady
|
|
75
|
+
? "自定义布局未解析(python3 缺席 / 旧装配 / 配置非法),按默认 product 判定"
|
|
76
|
+
: undefined;
|
|
52
77
|
|
|
53
78
|
// 开户产物 = 上面 Onboarding 注里点名的两个文件
|
|
54
|
-
const hasPrd = existsSync(join(
|
|
79
|
+
const hasPrd = existsSync(join(prdAbs, "prd.md"));
|
|
55
80
|
const hasStructureMeta = existsSync(
|
|
56
81
|
join(cwd, ".trellis", "spec", "structure", "_meta.json"),
|
|
57
82
|
);
|
|
@@ -74,6 +99,9 @@ function detectAtlasStatus(cwd: string): AtlasProjectStatus {
|
|
|
74
99
|
hasStackProfile,
|
|
75
100
|
onboarding,
|
|
76
101
|
version,
|
|
102
|
+
stackProfileRel,
|
|
103
|
+
prdRel,
|
|
104
|
+
layoutNote,
|
|
77
105
|
};
|
|
78
106
|
}
|
|
79
107
|
|
|
@@ -106,14 +134,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
106
134
|
` - Atlas 已装配: ${status.isAtlasProject ? `是 (版本: ${status.version || "dev"})` : "否"}`,
|
|
107
135
|
` - 开户态: ${ONBOARDING_LABEL[status.onboarding]}`,
|
|
108
136
|
` - Trellis 接入: ${status.hasTrellis ? "已接入" : "未检测到 (.trellis/ 缺失)"}`,
|
|
109
|
-
` - 技术栈画像: ${status.hasStackProfile ?
|
|
137
|
+
` - 技术栈画像: ${status.hasStackProfile ? `已配置 (${status.stackProfileRel})` : "未配置"}`,
|
|
110
138
|
];
|
|
139
|
+
if (status.layoutNote) {
|
|
140
|
+
lines.push(` - 布局: ${status.layoutNote}`);
|
|
141
|
+
}
|
|
111
142
|
if (!status.isAtlasProject) {
|
|
112
143
|
lines.push(``, `提示: 当前项目尚未接入 Atlas。使用 \`/atlas init\` 即可一键装配。`);
|
|
113
144
|
} else if (status.onboarding !== "done") {
|
|
114
145
|
lines.push(
|
|
115
146
|
``,
|
|
116
|
-
`提示: **装配 ≠ 开户** —— 开户产物 =
|
|
147
|
+
`提示: **装配 ≠ 开户** —— 开户产物 = ${status.prdRel}/prd.md 与 .trellis/spec/structure/_meta.json。`,
|
|
117
148
|
` 现在${ONBOARDING_LABEL[status.onboarding]};用户说「把项目接入 Atlas 工作流」时`,
|
|
118
149
|
` 直接唤起技能 \`/atlas-apply adopt\`(存量:只登记既有基线 + 开户最小集,`,
|
|
119
150
|
` 不重写老 PRD/用例)。开户按产物存在与否断点续跑,不吃掉已完成的部分。`,
|
|
@@ -216,11 +247,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
216
247
|
`\n\n# Atlas: 已装配但尚未开户(装配 ≠ 开户)\n` +
|
|
217
248
|
`This project has the Atlas harness **assembled** but is **not onboarded** ` +
|
|
218
249
|
`(onboarding state: ${status.onboarding}). Onboarding artifacts are ` +
|
|
219
|
-
|
|
250
|
+
`\`${status.prdRel}/prd.md\` and \`.trellis/spec/structure/_meta.json\` — both still missing.\n` +
|
|
220
251
|
`When the user asks to 「把项目接入 Atlas 工作流」/「接入 Atlas」, run the skill ` +
|
|
221
252
|
`\`atlas-apply adopt\` (existing project: register the baseline + minimal PRD only; ` +
|
|
222
253
|
`do NOT rewrite old PRDs or invent legacy test cases). **Do not** conclude "already ` +
|
|
223
|
-
`onboarded" just because \`.atlas/\` or
|
|
254
|
+
`onboarded" just because \`.atlas/\` or the asset root exist, and do not ask the user ` +
|
|
224
255
|
`whether they already onboarded. Onboarding resumes by artifact presence.\n`;
|
|
225
256
|
return { systemPrompt: prompt + onboardingNotice };
|
|
226
257
|
}
|
package/install.sh
CHANGED
|
@@ -4,26 +4,30 @@
|
|
|
4
4
|
#
|
|
5
5
|
# 用法:
|
|
6
6
|
# bash install.sh [--target <项目根>] [--update] [--force] [--dry-run] [--no-skill] [--no-patch] [--no-wiring]
|
|
7
|
+
# [--assets-root <dir>] [--adr-dir <dir>]
|
|
7
8
|
#
|
|
8
9
|
# 装配行为:
|
|
9
10
|
# 1. 整包装配到 <目标>/.atlas/(shared / rings / apply / templates / adapters /
|
|
10
|
-
# validators / patches / tests)。.atlas/
|
|
11
|
+
# validators / patches / tests / _support / extensions)。.atlas/ 属包产物:白名单子目录默认刷新,
|
|
12
|
+
# `.atlas/runs/`(运行态:E2E 汇总 / check 事件 / write-claims)保留不删。
|
|
11
13
|
# 2. 每个 skills/atlas-*/ 以一个瘦桩 SKILL.md 装到各平台 skill 目录。
|
|
12
14
|
# 3. 清理历史遗留的单入口 skill(<平台>/skills/atlas)。
|
|
13
|
-
# 4.
|
|
15
|
+
# 4. 建资产根骨架(默认 product/;已有内容永不覆盖)。
|
|
14
16
|
# 5. 应用 .trellis/workflow.md 的 Plan 挂载补丁(atlas apply;锚点幂等,
|
|
15
17
|
# 用 --no-patch 跳过)。
|
|
16
|
-
# 6. 项目接线:AGENTS.md 的 atlas 指针段 + .trellis/config.yaml 的
|
|
18
|
+
# 6. 项目接线:AGENTS.md 的 atlas 指针段 + .trellis/config.yaml 的 after_archive
|
|
17
19
|
# hook(幂等,用 --no-wiring 跳过)。
|
|
18
20
|
# 第 5、6 步属 global-rules §7 确认门动作,由本次显式安装触发。
|
|
19
21
|
#
|
|
20
22
|
# 模式:
|
|
21
23
|
# --update: 刷新已有项目的 .atlas/ 资产与平台 Skills 瘦桩(要求目标已初始化 .atlas/)。
|
|
24
|
+
# --assets-root / --adr-dir: 首次安装选择项目级资产根(默认 product/)与可选 ADR 根;
|
|
25
|
+
# 写入项目根 atlas.config.yaml(git 跟踪)。--update 只读配置,不重新选择。
|
|
22
26
|
# --update 的触点守卫:已跟踪触点存在未提交改动 ⇒ 拒(--force 越过);触点整目录
|
|
23
27
|
# 未跟踪(如 .atlas/ 不入库)⇒ WARN 放行、不消费 --force(覆盖自负,先回灌更稳)。
|
|
24
28
|
#
|
|
25
29
|
# 幂等:.atlas/ 与 skill 瘦桩默认刷新(属包下发物,用 --no-skill 跳过 skill);
|
|
26
|
-
#
|
|
30
|
+
# 资产根下的既有内容永不覆盖;workflow.md 补丁只做锚点插入、重跑即恢复;
|
|
27
31
|
# AGENTS.md 指针段与 config.yaml hook 就地更新、重跑即恢复。
|
|
28
32
|
#
|
|
29
33
|
set -euo pipefail
|
|
@@ -37,6 +41,8 @@ DRY_RUN=0
|
|
|
37
41
|
INSTALL_SKILL=1
|
|
38
42
|
INSTALL_PATCH=1
|
|
39
43
|
INSTALL_WIRING=1
|
|
44
|
+
ASSETS_ROOT_ARG=""
|
|
45
|
+
ADR_DIR_ARG=""
|
|
40
46
|
|
|
41
47
|
while [ $# -gt 0 ]; do
|
|
42
48
|
case "$1" in
|
|
@@ -47,8 +53,10 @@ while [ $# -gt 0 ]; do
|
|
|
47
53
|
--no-skill) INSTALL_SKILL=0; shift ;;
|
|
48
54
|
--no-patch) INSTALL_PATCH=0; shift ;;
|
|
49
55
|
--no-wiring) INSTALL_WIRING=0; shift ;;
|
|
56
|
+
--assets-root) ASSETS_ROOT_ARG="${2:-}"; shift 2 ;;
|
|
57
|
+
--adr-dir) ADR_DIR_ARG="${2:-}"; shift 2 ;;
|
|
50
58
|
-h|--help)
|
|
51
|
-
sed -n '2,
|
|
59
|
+
sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
52
60
|
exit 0 ;;
|
|
53
61
|
*) echo "未知参数: $1" >&2; exit 2 ;;
|
|
54
62
|
esac
|
|
@@ -193,6 +201,127 @@ if [ "${#PLATFORM_DIRS[@]}" -eq 0 ]; then
|
|
|
193
201
|
PLATFORM_DIRS=(".agents/skills")
|
|
194
202
|
fi
|
|
195
203
|
|
|
204
|
+
# ---------------------------------------------------------------- 布局配置
|
|
205
|
+
|
|
206
|
+
# 布局锚点 = 项目根 atlas.config.yaml(git 跟踪);首次安装由本脚本写,--update 只读。
|
|
207
|
+
# 配置缺失 ⇒ 默认 product/(存量项目零变化)。
|
|
208
|
+
CONFIG_FILE="$TARGET/atlas.config.yaml"
|
|
209
|
+
LAYOUT_PY="$SCRIPT_DIR/_support/layout.py"
|
|
210
|
+
if [ ! -f "$LAYOUT_PY" ]; then
|
|
211
|
+
say "错误:源包缺少 _support/layout.py,无法解析项目级资产根。"
|
|
212
|
+
exit 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
layout_print() { python3 "$LAYOUT_PY" --root "$TARGET" --print "$1"; }
|
|
216
|
+
layout_validate() { python3 "$LAYOUT_PY" --root "$TARGET" --validate >/dev/null; }
|
|
217
|
+
|
|
218
|
+
if [ "$IS_UPDATE" -eq 1 ]; then
|
|
219
|
+
if ! layout_validate; then
|
|
220
|
+
say "错误:布局配置非法(${CONFIG_FILE});先修正再 update。"
|
|
221
|
+
python3 "$LAYOUT_PY" --root "$TARGET" --validate || true
|
|
222
|
+
exit 2
|
|
223
|
+
fi
|
|
224
|
+
ASSETS_ROOT="$(layout_print assets_root)" || exit 2
|
|
225
|
+
ADR_DIR_REL="$(layout_print adr)" || exit 2
|
|
226
|
+
if [ -n "$ASSETS_ROOT_ARG" ] && [ "$ASSETS_ROOT_ARG" != "$ASSETS_ROOT" ]; then
|
|
227
|
+
say "错误:--assets-root=$ASSETS_ROOT_ARG 与现有配置(${ASSETS_ROOT})不一致;update 不换根。"
|
|
228
|
+
exit 2
|
|
229
|
+
fi
|
|
230
|
+
if [ -n "$ADR_DIR_ARG" ] && [ "$ADR_DIR_ARG" != "$ADR_DIR_REL" ]; then
|
|
231
|
+
say "错误:--adr-dir=$ADR_DIR_ARG 与现有配置(${ADR_DIR_REL})不一致;update 不换根。"
|
|
232
|
+
exit 2
|
|
233
|
+
fi
|
|
234
|
+
elif [ -f "$CONFIG_FILE" ]; then
|
|
235
|
+
if ! layout_validate; then
|
|
236
|
+
say "错误:布局配置非法(${CONFIG_FILE})。"
|
|
237
|
+
python3 "$LAYOUT_PY" --root "$TARGET" --validate || true
|
|
238
|
+
exit 2
|
|
239
|
+
fi
|
|
240
|
+
ASSETS_ROOT="$(layout_print assets_root)" || exit 2
|
|
241
|
+
ADR_DIR_REL="$(layout_print adr)" || exit 2
|
|
242
|
+
if [ -n "$ASSETS_ROOT_ARG" ] && [ "$ASSETS_ROOT_ARG" != "$ASSETS_ROOT" ]; then
|
|
243
|
+
say "错误:--assets-root=$ASSETS_ROOT_ARG 与现有配置(${ASSETS_ROOT})不一致。"
|
|
244
|
+
exit 2
|
|
245
|
+
fi
|
|
246
|
+
if [ -n "$ADR_DIR_ARG" ] && [ "$ADR_DIR_ARG" != "$ADR_DIR_REL" ]; then
|
|
247
|
+
say "错误:--adr-dir=$ADR_DIR_ARG 与现有配置(${ADR_DIR_REL})不一致。"
|
|
248
|
+
exit 2
|
|
249
|
+
fi
|
|
250
|
+
else
|
|
251
|
+
NEW_ASSETS_ROOT="${ASSETS_ROOT_ARG:-product}"
|
|
252
|
+
NEW_ADR_DIR="${ADR_DIR_ARG:-}"
|
|
253
|
+
python3 "$LAYOUT_PY" --root "$TARGET" --validate-value assets_root "$NEW_ASSETS_ROOT" || exit 2
|
|
254
|
+
if [ -n "$NEW_ADR_DIR" ]; then
|
|
255
|
+
python3 "$LAYOUT_PY" --root "$TARGET" --validate-value adr "$NEW_ADR_DIR" || exit 2
|
|
256
|
+
fi
|
|
257
|
+
if [ "$NEW_ASSETS_ROOT" != "product" ]; then
|
|
258
|
+
for p in stack-profile.yaml prd e2e adr design.md reviews atlas-reports; do
|
|
259
|
+
if [ -e "$TARGET/product/$p" ]; then
|
|
260
|
+
say "错误:所选资产根 $NEW_ASSETS_ROOT 不是默认 product,但 product/$p 已存在(双根风险)。"
|
|
261
|
+
say " 先手工迁移既有资产到 $NEW_ASSETS_ROOT/,或明确改用 --assets-root product。"
|
|
262
|
+
exit 2
|
|
263
|
+
fi
|
|
264
|
+
done
|
|
265
|
+
fi
|
|
266
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
267
|
+
say " [dry-run] + atlas.config.yaml(assets_root: ${NEW_ASSETS_ROOT})"
|
|
268
|
+
[ -n "$NEW_ADR_DIR" ] && say " adr: $NEW_ADR_DIR"
|
|
269
|
+
ASSETS_ROOT="$NEW_ASSETS_ROOT"
|
|
270
|
+
ADR_DIR_REL="${NEW_ADR_DIR:-$ASSETS_ROOT/adr}"
|
|
271
|
+
STACK_PROFILE_REL="$ASSETS_ROOT/stack-profile.yaml"
|
|
272
|
+
PRD_DIR_REL="$ASSETS_ROOT/prd"
|
|
273
|
+
E2E_DIR_REL="$ASSETS_ROOT/e2e"
|
|
274
|
+
E2E_CASES_REL="$E2E_DIR_REL/cases"
|
|
275
|
+
E2E_SCRIPTS_REL="$E2E_DIR_REL/scripts"
|
|
276
|
+
E2E_INDEX_REL="$E2E_DIR_REL/e2e-index.md"
|
|
277
|
+
E2E_REVIEWS_REL="$E2E_DIR_REL/reviews"
|
|
278
|
+
CONSOLE_LEDGER_REL="$E2E_REVIEWS_REL/console/events.jsonl"
|
|
279
|
+
else
|
|
280
|
+
WRITE_ARGS=(--root "$TARGET" --write --assets-root "$NEW_ASSETS_ROOT")
|
|
281
|
+
[ -n "$NEW_ADR_DIR" ] && WRITE_ARGS+=(--adr "$NEW_ADR_DIR")
|
|
282
|
+
python3 "$LAYOUT_PY" "${WRITE_ARGS[@]}" >/dev/null || exit 2
|
|
283
|
+
say " + atlas.config.yaml(assets_root: ${NEW_ASSETS_ROOT})"
|
|
284
|
+
[ -n "$NEW_ADR_DIR" ] && say " adr: $NEW_ADR_DIR"
|
|
285
|
+
ASSETS_ROOT="$(layout_print assets_root)" || exit 2
|
|
286
|
+
ADR_DIR_REL="$(layout_print adr)" || exit 2
|
|
287
|
+
STACK_PROFILE_REL="$(layout_print stack_profile)" || exit 2
|
|
288
|
+
PRD_DIR_REL="$(layout_print prd)" || exit 2
|
|
289
|
+
E2E_DIR_REL="$(layout_print e2e)" || exit 2
|
|
290
|
+
E2E_CASES_REL="$(layout_print e2e_cases)" || exit 2
|
|
291
|
+
E2E_SCRIPTS_REL="$(layout_print e2e_scripts)" || exit 2
|
|
292
|
+
E2E_INDEX_REL="$(layout_print e2e_index)" || exit 2
|
|
293
|
+
E2E_REVIEWS_REL="$(layout_print e2e_reviews)" || exit 2
|
|
294
|
+
CONSOLE_LEDGER_REL="$(layout_print console_ledger)" || exit 2
|
|
295
|
+
fi
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
if [ "$IS_UPDATE" -eq 0 ] && [ -f "$CONFIG_FILE" ]; then
|
|
299
|
+
STACK_PROFILE_REL="$(layout_print stack_profile)" || exit 2
|
|
300
|
+
PRD_DIR_REL="$(layout_print prd)" || exit 2
|
|
301
|
+
E2E_DIR_REL="$(layout_print e2e)" || exit 2
|
|
302
|
+
E2E_CASES_REL="$(layout_print e2e_cases)" || exit 2
|
|
303
|
+
E2E_SCRIPTS_REL="$(layout_print e2e_scripts)" || exit 2
|
|
304
|
+
E2E_INDEX_REL="$(layout_print e2e_index)" || exit 2
|
|
305
|
+
E2E_REVIEWS_REL="$(layout_print e2e_reviews)" || exit 2
|
|
306
|
+
CONSOLE_LEDGER_REL="$(layout_print console_ledger)" || exit 2
|
|
307
|
+
fi
|
|
308
|
+
if [ "$IS_UPDATE" -eq 1 ]; then
|
|
309
|
+
STACK_PROFILE_REL="$(layout_print stack_profile)" || exit 2
|
|
310
|
+
PRD_DIR_REL="$(layout_print prd)" || exit 2
|
|
311
|
+
E2E_DIR_REL="$(layout_print e2e)" || exit 2
|
|
312
|
+
E2E_CASES_REL="$(layout_print e2e_cases)" || exit 2
|
|
313
|
+
E2E_SCRIPTS_REL="$(layout_print e2e_scripts)" || exit 2
|
|
314
|
+
E2E_INDEX_REL="$(layout_print e2e_index)" || exit 2
|
|
315
|
+
E2E_REVIEWS_REL="$(layout_print e2e_reviews)" || exit 2
|
|
316
|
+
CONSOLE_LEDGER_REL="$(layout_print console_ledger)" || exit 2
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
STACK_PROFILE="$TARGET/$STACK_PROFILE_REL"
|
|
320
|
+
E2E_CASES_DIR="$TARGET/$E2E_CASES_REL"
|
|
321
|
+
say " 资产根 : $ASSETS_ROOT"
|
|
322
|
+
say " ADR 根 : $ADR_DIR_REL"
|
|
323
|
+
say ""
|
|
324
|
+
|
|
196
325
|
# ---------------------------------------------------------------- 1. 整包 → .atlas/
|
|
197
326
|
|
|
198
327
|
say "[1/6] 装配 atlas 包到 .atlas/"
|
|
@@ -278,17 +407,27 @@ say "[4/6] 建立骨架"
|
|
|
278
407
|
for d in \
|
|
279
408
|
".trellis/spec/structure/domains" \
|
|
280
409
|
".trellis/spec/structure/global" \
|
|
281
|
-
"
|
|
410
|
+
"$E2E_CASES_REL"
|
|
282
411
|
do
|
|
283
412
|
run mkdir -p "$TARGET/$d"
|
|
284
413
|
[ -f "$TARGET/$d/.gitkeep" ] || run touch "$TARGET/$d/.gitkeep"
|
|
285
414
|
done
|
|
286
415
|
|
|
287
|
-
if [ -f "$
|
|
288
|
-
say " =
|
|
416
|
+
if [ -f "$STACK_PROFILE" ]; then
|
|
417
|
+
say " = $STACK_PROFILE_REL 已存在,保留"
|
|
289
418
|
else
|
|
290
|
-
|
|
291
|
-
|
|
419
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
420
|
+
say " [dry-run] + ${STACK_PROFILE_REL}(由模板按资产根渲染)"
|
|
421
|
+
else
|
|
422
|
+
mkdir -p "$(dirname "$STACK_PROFILE")"
|
|
423
|
+
python3 - "$SCRIPT_DIR/templates/stack-profile.yaml" "$STACK_PROFILE" "$ASSETS_ROOT" <<'PY'
|
|
424
|
+
import sys
|
|
425
|
+
src, dst, assets_root = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
426
|
+
text = open(src, encoding="utf-8").read().replace("{{ASSETS_ROOT}}", assets_root)
|
|
427
|
+
open(dst, "w", encoding="utf-8").write(text)
|
|
428
|
+
PY
|
|
429
|
+
say " + ${STACK_PROFILE_REL}(草稿,需人工确认)"
|
|
430
|
+
fi
|
|
292
431
|
fi
|
|
293
432
|
|
|
294
433
|
# ---------------------------------------------------------------- 5. Trellis workflow 补丁(遍历 patches/*/)
|
|
@@ -344,13 +483,13 @@ if [ ! -f "$WIRING_DIR/apply-wiring.py" ] && [ -f "$SCRIPT_DIR/patches/project-w
|
|
|
344
483
|
WIRING_DIR="$SCRIPT_DIR/patches/project-wiring"
|
|
345
484
|
fi
|
|
346
485
|
if [ "$INSTALL_WIRING" -eq 1 ]; then
|
|
347
|
-
say "[6/6] 项目接线(AGENTS.md 指针 +
|
|
486
|
+
say "[6/6] 项目接线(AGENTS.md 指针 + after_archive hook)"
|
|
348
487
|
if [ -f "$WIRING_DIR/apply-wiring.py" ]; then
|
|
349
488
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
350
489
|
run python3 "$WIRING_DIR/apply-wiring.py" --target "$TARGET"
|
|
351
490
|
else
|
|
352
491
|
if python3 "$WIRING_DIR/apply-wiring.py" --target "$TARGET" --apply; then
|
|
353
|
-
say " -> AGENTS.md(atlas 指针段)+ .trellis/config.yaml(
|
|
492
|
+
say " -> AGENTS.md(atlas 指针段)+ .trellis/config.yaml(after_archive hook + session_auto_commit: false)"
|
|
354
493
|
else
|
|
355
494
|
say " ⚠ 接线存在 error——不阻断装配(见上)。"
|
|
356
495
|
fi
|
|
@@ -366,8 +505,8 @@ fi
|
|
|
366
505
|
|
|
367
506
|
# profile 声明了 graph.backend ⇒ 探测 cgc(缺 = 显式 WARN,不阻断——可选能力,
|
|
368
507
|
# 图谱消费者运行时按「not_ready / 响亮降级」三态处理;契约见 shared/stack-profile.md §2)。
|
|
369
|
-
if [ -f "$
|
|
370
|
-
GRAPH_BACKEND="$(python3 - "$
|
|
508
|
+
if [ -f "$STACK_PROFILE" ] && [ "$DRY_RUN" -eq 0 ]; then
|
|
509
|
+
GRAPH_BACKEND="$(python3 - "$STACK_PROFILE" <<'PY'
|
|
371
510
|
import re, sys
|
|
372
511
|
sec = None
|
|
373
512
|
for raw in open(sys.argv[1], encoding="utf-8"):
|
|
@@ -427,7 +566,9 @@ check "校验器 validate_testids" "$ATLAS_DEST/validators/validate_testids.py"
|
|
|
427
566
|
check "校验器 validate_e2e_index" "$ATLAS_DEST/validators/validate_e2e_index.py"
|
|
428
567
|
check "校验器 validate_design_exemptions" "$ATLAS_DEST/validators/validate_design_exemptions.py"
|
|
429
568
|
check "校验器 validate_e2e_mocks" "$ATLAS_DEST/validators/validate_e2e_mocks.py"
|
|
569
|
+
check "校验器 validate_ac_alignment" "$ATLAS_DEST/validators/validate_ac_alignment.py"
|
|
430
570
|
check "校验器 validate_adr" "$ATLAS_DEST/validators/validate_adr.py"
|
|
571
|
+
check "校验器 validate_walkthrough" "$ATLAS_DEST/validators/validate_walkthrough.py"
|
|
431
572
|
check "E2E 脚本生成器" "$ATLAS_DEST/scripts/gen_e2e_scripts.py"
|
|
432
573
|
check "结构事实刷新器" "$ATLAS_DEST/scripts/refresh_structure.py"
|
|
433
574
|
check "testid 薄桩生成器" "$ATLAS_DEST/scripts/gen_testid_stub.py"
|
|
@@ -438,25 +579,36 @@ check "E2E 评审控制台" "$ATLAS_DEST/scripts/e2e_console.py"
|
|
|
438
579
|
check "控制台静态资源" "$ATLAS_DEST/templates/e2e/console/index.html"
|
|
439
580
|
check "适配器脚手架" "$ATLAS_DEST/adapters/_scaffold.py"
|
|
440
581
|
check "适配器指南" "$ATLAS_DEST/adapters/README.md"
|
|
582
|
+
check "AC 提取器注册表" "$ATLAS_DEST/adapters/ac/__init__.py"
|
|
583
|
+
check "AC 提取器(pytest)" "$ATLAS_DEST/adapters/ac/pytest.py"
|
|
584
|
+
check "AC 提取器(Vitest/Jest)" "$ATLAS_DEST/adapters/ac/js.py"
|
|
585
|
+
check "AC 提取器(JUnit 5)" "$ATLAS_DEST/adapters/ac/junit.py"
|
|
586
|
+
check "AC 外部 extractor 运行器" "$ATLAS_DEST/adapters/ac/command.py"
|
|
587
|
+
check "JPA 模型解析器" "$ATLAS_DEST/adapters/_jpa.py"
|
|
441
588
|
check "图谱后端隔离层" "$ATLAS_DEST/adapters/_graph.py"
|
|
442
589
|
check "影响面基准报告器" "$ATLAS_DEST/scripts/impact_report.py"
|
|
443
590
|
check "回灌助手" "$ATLAS_DEST/scripts/backflow.py"
|
|
591
|
+
check "收口派生器" "$ATLAS_DEST/scripts/closeout_derive.py"
|
|
592
|
+
check "写面 claim 仲裁" "$ATLAS_DEST/scripts/write_claim.py"
|
|
444
593
|
check "共享内核(模块加载器)" "$ATLAS_DEST/_support/siblings.py"
|
|
445
594
|
check "共享内核(profile 解析)" "$ATLAS_DEST/_support/stack_profile.py"
|
|
446
595
|
check "共享内核(用例块解析)" "$ATLAS_DEST/_support/case_block.py"
|
|
596
|
+
check "共享内核(手工走查块解析)" "$ATLAS_DEST/_support/manual_block.py"
|
|
447
597
|
check "共享内核(用例 ID 文法)" "$ATLAS_DEST/_support/case_id.py"
|
|
448
598
|
check "共享内核(node 运行器判据)" "$ATLAS_DEST/_support/pw_runner.py"
|
|
449
599
|
check "共享内核(base query 转发)" "$ATLAS_DEST/_support/url_merge.py"
|
|
450
600
|
check "共享内核(审查策略单源)" "$ATLAS_DEST/_support/review_policy.py"
|
|
601
|
+
check "共享内核(布局解析)" "$ATLAS_DEST/_support/layout.py"
|
|
602
|
+
check "共享内核(维度解析)" "$ATLAS_DEST/_support/ac_coverage.py"
|
|
451
603
|
check "pi 宿主扩展" "$ATLAS_DEST/extensions/pi.ts"
|
|
452
604
|
check "结构模板" "$ATLAS_DEST/templates/structure/_meta.json"
|
|
453
605
|
check "PRD 模板" "$ATLAS_DEST/templates/prd/domain.md"
|
|
454
606
|
check "E2E 模板" "$ATLAS_DEST/templates/e2e/e2e-index.md"
|
|
455
607
|
check "ADR 模板" "$ATLAS_DEST/templates/adr/template.md"
|
|
456
608
|
check "审查指引模板" "$ATLAS_DEST/templates/reviews/README.md"
|
|
457
|
-
check "
|
|
609
|
+
check "stack-profile(资产根)" "$STACK_PROFILE"
|
|
458
610
|
check "structure spec 根" "$TARGET/.trellis/spec/structure"
|
|
459
|
-
check "e2e 用例分片目录" "$
|
|
611
|
+
check "e2e 用例分片目录" "$E2E_CASES_DIR"
|
|
460
612
|
|
|
461
613
|
if [ "$INSTALL_PATCH" -eq 1 ] && [ "$DRY_RUN" -ne 1 ] && [ -f "$TARGET/.trellis/workflow.md" ]; then
|
|
462
614
|
if grep -q "atlas:apply:plan-step-1-6" "$TARGET/.trellis/workflow.md"; then
|
|
@@ -473,9 +625,9 @@ if [ "$INSTALL_WIRING" -eq 1 ] && [ "$DRY_RUN" -ne 1 ]; then
|
|
|
473
625
|
say " [WARN] AGENTS.md atlas 指针段未生效(见 patches/project-wiring/README.md)"
|
|
474
626
|
fi
|
|
475
627
|
if [ -f "$TARGET/.trellis/config.yaml" ] && grep -q "refresh_structure.py" "$TARGET/.trellis/config.yaml"; then
|
|
476
|
-
say " [OK]
|
|
628
|
+
say " [OK] after_archive hook(refresh_structure)"
|
|
477
629
|
else
|
|
478
|
-
say " [WARN]
|
|
630
|
+
say " [WARN] after_archive hook 未生效(见 patches/project-wiring/README.md)"
|
|
479
631
|
fi
|
|
480
632
|
fi
|
|
481
633
|
|
|
@@ -518,6 +670,14 @@ print(stamp_file(Path(sys.argv[1]), Path(sys.argv[2])))
|
|
|
518
670
|
PY
|
|
519
671
|
)"; then
|
|
520
672
|
say " [ok] .atlas/VERSION 装配版本戳(${OUT})"
|
|
673
|
+
# RFC-20261023-02 ②(2026-10-23):装配基线 manifest 必须与戳同点落下——落戳成功而
|
|
674
|
+
# manifest 缺失 = 逐文件对账面不存在,按装配失败处理(硬错,与 F37 同款理由)。
|
|
675
|
+
if [ -s "$TARGET/.atlas/MANIFEST.json" ]; then
|
|
676
|
+
say " [ok] .atlas/MANIFEST.json 装配基线(逐文件摘要)"
|
|
677
|
+
else
|
|
678
|
+
say " [FAIL] 装配基线 manifest 未落下(.atlas/MANIFEST.json)⇒ 装配态无法逐文件对账。"
|
|
679
|
+
exit 1
|
|
680
|
+
fi
|
|
521
681
|
else
|
|
522
682
|
# 硬错而非 WARN(F37,2026-10-15):落戳失败 = 装配集为空(摘要算不出来)或脚本缺失。
|
|
523
683
|
# 「装配 0 文件」不得被记成一次合法装配,且 WARN 会让后续步骤照常往下走。
|
|
@@ -561,6 +721,6 @@ else
|
|
|
561
721
|
else
|
|
562
722
|
say "(未检测到既有源码:看起来是后者;也可以只说「跑一下三环全量构建,建立应然基线」)"
|
|
563
723
|
fi
|
|
564
|
-
say "Agent 会先带你核对
|
|
724
|
+
say "Agent 会先带你核对 ${STACK_PROFILE_REL}(origin / apps / role / adapters)"
|
|
565
725
|
say "再落盘;占位符替换前 atlas check 判 FAIL 属预期,不是装坏了。"
|
|
566
726
|
fi
|