frontend-project-context 1.0.0
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 +14 -0
- package/LICENSE +201 -0
- package/NOTICE +4 -0
- package/PROJECT_STATE.json +176 -0
- package/README.md +148 -0
- package/RTK.md +13 -0
- package/UPGRADING.md +15 -0
- package/bin/project-context.mjs +7 -0
- package/docs/00-PRODUCT-CONSTITUTION.md +166 -0
- package/docs/01-PRODUCT-CORE.md +143 -0
- package/docs/02-MARKET-BOUNDARY.md +88 -0
- package/docs/03-FINAL-SOLUTION.md +203 -0
- package/docs/04-PROGRAM-DESIGN.md +428 -0
- package/docs/05-ACCEPTANCE-CONTRACT.md +348 -0
- package/docs/06-HISTORICAL-PROTOTYPE.md +55 -0
- package/docs/07-REAL-TASK-EVIDENCE.md +52 -0
- package/docs/08-INSTALLATION-AND-DISTRIBUTION.md +199 -0
- package/docs/09-B0-DTG-TMC-MOBILE.md +173 -0
- package/docs/10-B0-DTG-TMC-PC.md +118 -0
- package/docs/11-V1-AUTHORING-CLOSURE-DESIGN.md +312 -0
- package/docs/12-KNOWLEDGE-MAINTENANCE-CLOSURE-ROADMAP.md +350 -0
- package/docs/13-READ-ONLY-GOVERNANCE-DASHBOARD-DESIGN.md +489 -0
- package/docs/14-FORMAL-RELEASE-READINESS.md +61 -0
- package/docs/15-SOURCE-LIFECYCLE-CLOSURE-DESIGN.md +260 -0
- package/docs/README.md +74 -0
- package/examples/README.md +17 -0
- package/examples/package.json +11 -0
- package/examples/project-context-check.yml +22 -0
- package/package.json +40 -0
- package/src/project-context/approver.mjs +177 -0
- package/src/project-context/authoring.mjs +190 -0
- package/src/project-context/canonical-json.mjs +55 -0
- package/src/project-context/checker.mjs +132 -0
- package/src/project-context/cli.mjs +409 -0
- package/src/project-context/contract-schema.mjs +316 -0
- package/src/project-context/dashboard-model.mjs +278 -0
- package/src/project-context/dashboard-renderer.mjs +637 -0
- package/src/project-context/discovery.mjs +251 -0
- package/src/project-context/errors.mjs +13 -0
- package/src/project-context/io.mjs +93 -0
- package/src/project-context/maintenance.mjs +400 -0
- package/src/project-context/path-policy.mjs +155 -0
- package/src/project-context/project-store.mjs +138 -0
- package/src/project-context/projection-store.mjs +107 -0
- package/src/project-context/renderer.mjs +135 -0
- package/src/project-context/scope-compiler.mjs +132 -0
- package/src/project-context/source-reader.mjs +124 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# 1.0.0 Source Lifecycle Closure 设计与实现记录
|
|
2
|
+
|
|
3
|
+
> 权威说明:本文只冻结首次公开发布前的来源生命周期修补范围。产品身份、内核、永久边界和不变量仍以 [00-PRODUCT-CONSTITUTION.md](./00-PRODUCT-CONSTITUTION.md) 为准;当前事实与授权以 [PROJECT_STATE.json](../PROJECT_STATE.json) 为准。
|
|
4
|
+
>
|
|
5
|
+
> 状态:`implemented-local-verified; implementation-authority-consumed; public-publication-authorized-authenticated`
|
|
6
|
+
>
|
|
7
|
+
> 审查基线:产品宪法 `1.1.1`、PROJECT_STATE schema `39`、package `1.0.0`、Contract schema 1、A-01 至 A-39 加 B0/CLI 共 43 项本地通过。实现后 PROJECT_STATE schema 为 41,Contract reader 支持 1/2,A-01 至 A-45 加 B0/CLI 共 49 项本地通过。
|
|
8
|
+
|
|
9
|
+
## 1. 设计结论
|
|
10
|
+
|
|
11
|
+
重大项目调整不需要新的框架扫描器、AI Runtime 或第二套治理系统。现有能力已经覆盖:
|
|
12
|
+
|
|
13
|
+
- `check` 全项目检测 source、contract、verification 和 projection 漂移;
|
|
14
|
+
- `dashboard` 汇总全部来源状态、引用关系、影响 item 和 projection;
|
|
15
|
+
- `review-source` 对单一来源给出当前 digest 与确定性影响集;
|
|
16
|
+
- `accept-source-change` 处理同一 locator 上仍可读取的内容变化;
|
|
17
|
+
- `revise`、`deprecate`、`approve --pending` 和 `publish` 完成 item 后续治理。
|
|
18
|
+
|
|
19
|
+
唯一无法通过稳定 CLI 关闭的情况是:**已登记本地来源被删除、搬迁或永久退役**。`accept-source-change` 明确拒绝 missing/unreadable;`register` 明确拒绝同 ID locator 变化;Contract schema 1 没有 source lifecycle,checker 会永久报告 missing。维护者最终只能直接编辑内部 JSON,这与已经声明的知识维护闭环不一致。
|
|
20
|
+
|
|
21
|
+
因此首次公开发布前只补 `Source Lifecycle Closure`:允许显式登记替代来源、把受影响 item 修订到替代来源,并在无当前知识继续依赖时显式废弃旧来源。默认仍 fail closed,任何来源或规范都不会自动批准。
|
|
22
|
+
|
|
23
|
+
## 2. 需求分类
|
|
24
|
+
|
|
25
|
+
按产品宪法第 9 节,本缺口分类为 **内核缺陷**,不是新产品方向:
|
|
26
|
+
|
|
27
|
+
1. 来源注册与追溯属于固定内核;
|
|
28
|
+
2. `source-missing` 已经是稳定 finding,但没有无需手改 store 的安全恢复出口;
|
|
29
|
+
3. 该缺口在任意项目搬迁文件时都会出现,不依赖 Vue、React、路由或业务语义;
|
|
30
|
+
4. 修补只延长既有 source → item → approval → projection 生命周期,不改变产品身份。
|
|
31
|
+
|
|
32
|
+
自然语言需求到代码位置解析属于 Coding Agent 或可选消费者适配器,不进入本次内核修补。
|
|
33
|
+
|
|
34
|
+
## 3. 用户闭环
|
|
35
|
+
|
|
36
|
+
### 3.1 来源内容仍在原位置
|
|
37
|
+
|
|
38
|
+
继续使用既有流程,不增加命令:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
check/dashboard
|
|
42
|
+
→ review-source
|
|
43
|
+
→ accept-source-change --expected-digest ... --affected-items ... --write
|
|
44
|
+
→ revise/deprecate item
|
|
45
|
+
→ approve --pending
|
|
46
|
+
→ publish
|
|
47
|
+
→ check
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3.2 来源搬迁到新位置
|
|
51
|
+
|
|
52
|
+
来源 locator 是 provenance 身份的一部分,因此不在原 ID 上静默改路径:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
check/dashboard 确认旧来源 missing
|
|
56
|
+
→ register 新 source ID 与新 locator --write
|
|
57
|
+
→ revise 受影响 item,改为引用新 source ID --write
|
|
58
|
+
→ approve --pending --ids ... --write
|
|
59
|
+
→ deprecate-source 旧 source ID --write
|
|
60
|
+
→ publish --write
|
|
61
|
+
→ check
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
旧来源作为 deprecated provenance 留在 Contract,Git 历史之外仍能解释旧 item;它不再读取文件、不进入 source lock,也不支持新 proposed/approved item 引用。
|
|
65
|
+
|
|
66
|
+
### 3.3 来源永久退役
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
check/dashboard
|
|
70
|
+
→ revise 或 deprecate 所有仍引用旧来源的 proposed/approved item
|
|
71
|
+
→ approve 需要保留的 revision
|
|
72
|
+
→ deprecate-source --expected-source-digest ... --by ... --rationale ... --write
|
|
73
|
+
→ publish --write
|
|
74
|
+
→ check
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`deprecate-source` 不级联修改 item、不删除审计记录、不自动选择替代来源。
|
|
78
|
+
|
|
79
|
+
## 4. 最小 CLI 合同
|
|
80
|
+
|
|
81
|
+
只新增一个命令:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
project-context deprecate-source --project PATH --id SOURCE_ID
|
|
85
|
+
[--expected-source-digest SHA256] --by NAME --rationale TEXT
|
|
86
|
+
[--write] [--json]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
行为固定为:
|
|
90
|
+
|
|
91
|
+
- 默认 preview;`--write` 必须同时提供本命令自己的 `--expected-source-digest`;
|
|
92
|
+
- digest 是当前 canonical source object 的 digest,不是文件当前内容 digest;preview 和 `review-source --json` 都返回该值;
|
|
93
|
+
- source 必须存在且仍为 active;已 deprecated 返回稳定输入错误;
|
|
94
|
+
- 任何 proposed 或 approved item 仍通过 `sources` 或 `verification.source` 引用它时拒绝写入,并返回稳定引用列表;
|
|
95
|
+
- deprecated item 可以继续引用 deprecated source,以保留历史 provenance;
|
|
96
|
+
- 本地来源可以处于 changed、missing 或 unreadable,废弃动作不要求文件恢复;
|
|
97
|
+
- 成功时把 source 标记为 deprecated,记录 `by/at/rationale`,并从 source lock 删除对应 checkpoint;
|
|
98
|
+
- 不改 item、不批准内容、不删除 source、不写 projection;Contract digest 变化后既有 projection 自然 stale;
|
|
99
|
+
- 写前重新核对 Contract、source lock、projection lock 快照和 source object digest;状态变化时零写入。
|
|
100
|
+
|
|
101
|
+
`register`、`propose`、`approve`、`revise`、`check`、`review-source`、`dashboard` 和 `publish` 只做 schema 2 兼容调整,不新增其他用户入口。
|
|
102
|
+
|
|
103
|
+
## 5. 数据模型
|
|
104
|
+
|
|
105
|
+
### 5.1 Contract schema 2
|
|
106
|
+
|
|
107
|
+
source 增加显式 lifecycle:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"id": "source-old-guide",
|
|
112
|
+
"kind": "file",
|
|
113
|
+
"path": "docs/old-guide.md",
|
|
114
|
+
"digest": "sha256:...",
|
|
115
|
+
"status": "deprecated",
|
|
116
|
+
"deprecation": {
|
|
117
|
+
"by": "maintainer",
|
|
118
|
+
"at": "2026-09-08T00:00:00.000Z",
|
|
119
|
+
"rationale": "Replaced by source-new-guide after the documentation restructure."
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
规则:
|
|
125
|
+
|
|
126
|
+
- schema 2 的每个 source 必须有 `status: active | deprecated`;
|
|
127
|
+
- active source 不允许 `deprecation`;deprecated source 必须有完整 deprecation;
|
|
128
|
+
- proposed/approved item 的 `sources` 和 `verification.source` 只能指向 active source;
|
|
129
|
+
- deprecated item 可以引用 active 或 deprecated source;
|
|
130
|
+
- deprecated local source 保留最后一次人工确认 digest,但 checker 不再读取 locator;
|
|
131
|
+
- source lock 只保存 active local source;deprecated source 的 lock entry 视为 orphan finding;
|
|
132
|
+
- 不增加 source kind、item kind、scope kind、projection target 或新 store 文件。
|
|
133
|
+
|
|
134
|
+
### 5.2 Proposal 与 lock
|
|
135
|
+
|
|
136
|
+
- proposal 继续 schema 1;proposal source 没有 lifecycle 字段,合并到 schema 2 Contract 时按 active 处理;
|
|
137
|
+
- source lock 和 projection lock 继续 schema 1;
|
|
138
|
+
- renderer 继续 version 3;
|
|
139
|
+
- Dashboard View Model 从 2 升为 3,只增加 source `deprecated` 状态和 deprecation 审计字段,不复制来源正文。
|
|
140
|
+
|
|
141
|
+
### 5.3 兼容迁移
|
|
142
|
+
|
|
143
|
+
- reader 同时接受 Contract schema 1 和 2;schema 1 source 在内存中解释为 active,但只读命令不得因此写盘;
|
|
144
|
+
- 既有普通命令对 schema 1 项目保持原字节和行为;
|
|
145
|
+
- 第一次成功执行 `deprecate-source --write` 时,把全部现有 source 显式标为 active、目标 source 标为 deprecated,并原子写成 schema 2;
|
|
146
|
+
- schema 2 一旦写入,不自动降级;旧 1.0.0-RC reader 不支持 schema 2,升级说明必须明确;
|
|
147
|
+
- 现有 schema 1 项目无需预先运行 `migrate`,不新增迁移命令。
|
|
148
|
+
|
|
149
|
+
## 6. 影响集与人工权限
|
|
150
|
+
|
|
151
|
+
实现必须复用 `sourceImpact` 和现有 item 引用检查:
|
|
152
|
+
|
|
153
|
+
1. preview 显示 direct、verification、override-dependent、fallback 与 projection 集合;
|
|
154
|
+
2. deprecation 的阻断引用集合覆盖所有 proposed/approved item 的 `sources` 与 `verification.source`;
|
|
155
|
+
3. 不因文件名、source 内容、subject 或 scope 猜测替代关系;
|
|
156
|
+
4. 新来源必须先通过既有 `register --write` 显式登记;
|
|
157
|
+
5. item 必须通过既有 `revise --write` 进入 pending,再通过 `approve --pending --write` 明确批准;
|
|
158
|
+
6. source deprecation 必须有本命令自己的署名、理由、baseline digest 和 `--write`。
|
|
159
|
+
|
|
160
|
+
不存在自动接受、自动迁移 locator、自动 revision、自动批准或自动 publish。
|
|
161
|
+
|
|
162
|
+
## 7. 原子性与失败恢复
|
|
163
|
+
|
|
164
|
+
`deprecate-source` 同时改变 Contract 和 source lock,复用现有 `writeProjectState` 的 lock-first 顺序:
|
|
165
|
+
|
|
166
|
+
1. 从已加载 snapshot 构造并完整验证 next schema 2 Contract 与 next source lock;
|
|
167
|
+
2. 写前核对 contract/source/projection lock digest 和 source object digest;
|
|
168
|
+
3. 先写 next source lock,再写 next Contract;
|
|
169
|
+
4. 第二步失败时仅在当前 lock 仍等于本命令写入值时恢复旧 lock;
|
|
170
|
+
5. 恢复失败或遭遇并发时,最终状态必须被 `check` 识别为 source-lock mismatch/orphan,不能静默编译;
|
|
171
|
+
6. 不新增文件锁、数据库、history ledger、retry 或后台任务。
|
|
172
|
+
|
|
173
|
+
## 8. Finding、错误类别与退出码
|
|
174
|
+
|
|
175
|
+
现有退出码 0–4 不变。
|
|
176
|
+
|
|
177
|
+
新增或扩展的稳定类别:
|
|
178
|
+
|
|
179
|
+
| 退出码 | code | 含义 |
|
|
180
|
+
| --- | --- | --- |
|
|
181
|
+
| 0 | `source-deprecation-preview` / 写入成功 | preview 或显式废弃成功 |
|
|
182
|
+
| 1 | `source-baseline-changed`、`project-state-changed` | baseline 或项目快照已变化 |
|
|
183
|
+
| 2 | `source-not-found`、`source-already-deprecated`、`source-still-referenced`、参数/schema 错误 | 当前状态不允许废弃 |
|
|
184
|
+
| 3 | 既有 projection ownership conflict | 所有权冲突语义不变 |
|
|
185
|
+
| 4 | `internal-error` | 非预期失败 |
|
|
186
|
+
|
|
187
|
+
checker/dashboard 扩展:
|
|
188
|
+
|
|
189
|
+
- active local source 继续产生 changed/missing/unreadable;
|
|
190
|
+
- deprecated source 不产生文件漂移 finding;
|
|
191
|
+
- deprecated source 仍在 source lock 时产生 `source-lock-deprecated`;
|
|
192
|
+
- proposed/approved item 引用 deprecated source 时产生或抛出 `source-reference-deprecated`;
|
|
193
|
+
- `context`/`publish` 对上述 source finding 继续 fail closed。
|
|
194
|
+
|
|
195
|
+
## 9. 唯一实现范围
|
|
196
|
+
|
|
197
|
+
获得单独实现授权后,只允许以下变化:
|
|
198
|
+
|
|
199
|
+
| 文件/模块 | 唯一允许变化 |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| `contract-schema.mjs` | 读取 Contract schema 1/2;验证 source lifecycle 和引用约束 |
|
|
202
|
+
| `maintenance.mjs` | source object digest、deprecate preview/commit 和引用预检 |
|
|
203
|
+
| `authoring.mjs` / `approver.mjs` | schema 2 Contract 中新增 source 按 active 合并;拒绝批准 deprecated source 引用 |
|
|
204
|
+
| `checker.mjs` / `source-reader.mjs` | 跳过 deprecated source IO;补稳定 lifecycle finding |
|
|
205
|
+
| `project-store.mjs` | 保持既有快照与 lock-first 写入,支持 schema 2 Contract bytes |
|
|
206
|
+
| `dashboard-model.mjs` / `dashboard-renderer.mjs` | 只增加 deprecated source 展示;View Model schema 3 |
|
|
207
|
+
| `cli.mjs` | `deprecate-source`、help、allowlist 和稳定输出 |
|
|
208
|
+
| `test/project-context/` | A-40 至 A-45;既有 43 项不得删除或弱化 |
|
|
209
|
+
| README、UPGRADING、CHANGELOG、04、05、08、14、15、RTK、PROJECT_STATE、自托管 Contract | 实现完成后同步真实结果 |
|
|
210
|
+
|
|
211
|
+
明确不修改 discovery 识别范围、scope compiler、Context 内容、projection target 或安装模型。
|
|
212
|
+
|
|
213
|
+
## 10. 冻结验收 A-40 至 A-45
|
|
214
|
+
|
|
215
|
+
- **A-40 缺失来源可恢复**:missing active source 阻断;登记替代来源、revision/reapproval、显式 deprecate-source 和 republish 后 check clean,全程不手改 store。
|
|
216
|
+
- **A-41 source deprecation 权限**:preview 零写入;缺 `--write`、baseline、by、rationale 或 baseline 不匹配均零写入;deprecated source 可重复识别但不可重复废弃。
|
|
217
|
+
- **A-42 引用安全**:任一 proposed/approved item 的 source 或 verification 仍引用目标时拒绝;deprecated item 可保留历史引用;不级联修改 item。
|
|
218
|
+
- **A-43 schema 1/2 兼容**:schema 1 继续可读且不发生隐式写;首次 deprecation 按固定规则升 schema 2;proposal/locks 保持 schema 1;旧 renderer 投影按既有规则 stale。
|
|
219
|
+
- **A-44 checker/dashboard/context 一致性**:active 与 deprecated source 分类一致;deprecated 不触发 IO;非法引用和 deprecated lock 被稳定发现;Context/Publish fail closed;看板双语解释完整。
|
|
220
|
+
- **A-45 原子性与永久边界**:模拟 Contract/source-lock/target source baseline 并发和第二步失败;最终可检查且不覆盖并发状态;全量 43 项继续通过,零 Provider、网络、dependency install、child process、Git、业务代码修改或真实项目访问。
|
|
221
|
+
|
|
222
|
+
实现 Gate:A-01 至 A-45、B0-01/B0-02、CLI 与 release pack 验收全部通过;README 能让另一位维护或维护者完成一次 missing → replacement → retirement → clean 的独立闭环。
|
|
223
|
+
|
|
224
|
+
## 11. 后续路线,但不在本次实现范围
|
|
225
|
+
|
|
226
|
+
### 11.1 CI 持续对账
|
|
227
|
+
|
|
228
|
+
现有 `context:check` 已可在 pull request 和 main push 自动发现漂移,`dashboard` 已可生成全局影响视图。后续只需改进示例,让外部 CI 在失败时保存 dashboard HTML/JSON artifact;这是接入适配,不需要任务调度器或新的核心扫描器。
|
|
229
|
+
|
|
230
|
+
### 11.2 Targeted Context 消费协议
|
|
231
|
+
|
|
232
|
+
“AI 指哪打哪”应拆成模型中立消费协议:Coding Agent 先解析候选路径,再调用现有 `context --path ...` 取得确定性 Contract 子集。路径解析、符号索引、调用图和自然语言推断属于 Agent/IDE/LSP 适配层,不能进入本次内核修补,也不能自动扩大写入权限。
|
|
233
|
+
|
|
234
|
+
是否设计该适配器必须在 Source Lifecycle Closure 实现、验收和首次发布决策之后单独授权。
|
|
235
|
+
|
|
236
|
+
## 12. 不做项与停止条件
|
|
237
|
+
|
|
238
|
+
本阶段不做:
|
|
239
|
+
|
|
240
|
+
- 自动接受 source change、自动批准、自动 revision/deprecation 或自动 publish;
|
|
241
|
+
- 自动修改业务代码、运行项目测试或执行真实开发任务;
|
|
242
|
+
- 自研 Agent Runtime、Provider、MCP、IDE、LSP、符号索引或调用图;
|
|
243
|
+
- 新增框架识别、source kind、item kind、scope kind 或 projection target;
|
|
244
|
+
- 批量 change-set、任务调度、daemon、watch、数据库、文件锁或 Git/Worktree 管理;
|
|
245
|
+
- 网络、依赖安装、真实业务项目、Git commit/tag/push 或 package publish。
|
|
246
|
+
|
|
247
|
+
## 13. 本地实现结果与停止
|
|
248
|
+
|
|
249
|
+
2026-09-08 经单独授权,本文第 9 节的唯一范围已经实现,没有推倒重写:
|
|
250
|
+
|
|
251
|
+
- Contract schema 1/2 reader、source lifecycle 验证、authoring/approval 引用边界已落地;
|
|
252
|
+
- `deprecate-source` 已提供 preview、source object digest baseline、阻断引用、署名/理由审计与显式写入;
|
|
253
|
+
- deprecated source 不再触发 locator IO,source lock checkpoint 被移除,旧 projection 因 Contract digest 变化自然 stale;
|
|
254
|
+
- project store 写前增加 projection lock snapshot 核对,双文件继续使用 lock-first 与条件恢复;
|
|
255
|
+
- Dashboard View Model 升为 3,并以中英标签显示 deprecation 审计;
|
|
256
|
+
- A-40 至 A-45 与全部既有测试通过,`npm run check` 为 49/49。
|
|
257
|
+
|
|
258
|
+
兼容结果:schema 1 项目可继续只读和普通维护且不会隐式写盘;只有首次成功来源废弃才确定性升级 Contract schema 2;proposal、source lock、projection lock 仍为 schema 1,renderer 仍为 3。自托管 Contract 当前没有需要废弃的来源,因此保持 schema 1,避免无业务理由的强制迁移。
|
|
259
|
+
|
|
260
|
+
本次实现授权已经完成并消耗。实现阶段未访问真实业务项目、Provider 或网络,未安装依赖,未执行 Git 或发布操作,也未新增第 12 节所列任何非目标。Source Lifecycle Closure 的发布保持条件已关闭;随后用户已单独授权完成 public npm 发布,发布者 npmjs.org CLI 身份已经验证。
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 文档索引
|
|
2
|
+
|
|
3
|
+
产品宪法是唯一规范真源;其余文档记录设计形成过程、实现说明或证据,不能反向改变产品定义。
|
|
4
|
+
|
|
5
|
+
## 唯一规范真源
|
|
6
|
+
|
|
7
|
+
0. [00-PRODUCT-CONSTITUTION.md](./00-PRODUCT-CONSTITUTION.md)
|
|
8
|
+
|
|
9
|
+
固定产品身份、内核、永久边界、v1 完成条件、真实项目规则、需求分类和阶段停止规则。动态的当前事实、授权和下一步由 `PROJECT_STATE.json` 唯一记录;其他文档与宪法冲突时,以宪法为准。
|
|
10
|
+
|
|
11
|
+
## 支持性设计文档
|
|
12
|
+
|
|
13
|
+
1. [01-PRODUCT-CORE.md](./01-PRODUCT-CORE.md)
|
|
14
|
+
|
|
15
|
+
定义用户问题、产品核心、长期资产、成功标准和非目标。
|
|
16
|
+
|
|
17
|
+
2. [02-MARKET-BOUNDARY.md](./02-MARKET-BOUNDARY.md)
|
|
18
|
+
|
|
19
|
+
定义哪些能力直接采用 Kiro、Ruler、Rulesync、Spec Kit 和成熟 Coding Agent,哪些差异才允许自研。
|
|
20
|
+
|
|
21
|
+
3. [03-FINAL-SOLUTION.md](./03-FINAL-SOLUTION.md)
|
|
22
|
+
|
|
23
|
+
冻结 Project Contract、Context Compiler、投影和漂移检查组成的最终产品方案。
|
|
24
|
+
|
|
25
|
+
4. [04-PROGRAM-DESIGN.md](./04-PROGRAM-DESIGN.md)
|
|
26
|
+
|
|
27
|
+
在产品方案之后给出 v1 可实现的程序结构、文件合同、命令和安全写入规则。
|
|
28
|
+
|
|
29
|
+
5. [05-ACCEPTANCE-CONTRACT.md](./05-ACCEPTANCE-CONTRACT.md)
|
|
30
|
+
|
|
31
|
+
记录程序验收、团队维护验收结果和真实价值验收设计;当前完成门槛以产品宪法第 7 节为准。
|
|
32
|
+
|
|
33
|
+
6. [08-INSTALLATION-AND-DISTRIBUTION.md](./08-INSTALLATION-AND-DISTRIBUTION.md)
|
|
34
|
+
|
|
35
|
+
定义项目内安装、静态投影、团队分发和后续发布边界;具体 v1 CLI 合同以 04 为准。
|
|
36
|
+
|
|
37
|
+
7. [11-V1-AUTHORING-CLOSURE-DESIGN.md](./11-V1-AUTHORING-CLOSURE-DESIGN.md)
|
|
38
|
+
|
|
39
|
+
记录 v1 authoring 闭环的冻结方案与本地实现结果:通用来源登记、scoped item 提案、显式批准、完整 bundle 内容、安全写入、兼容性和 A-15 至 A-20 验收。
|
|
40
|
+
|
|
41
|
+
8. [12-KNOWLEDGE-MAINTENANCE-CLOSURE-ROADMAP.md](./12-KNOWLEDGE-MAINTENANCE-CLOSURE-ROADMAP.md)
|
|
42
|
+
|
|
43
|
+
记录 `0.7.0` 冻结设计、本地实现与团队维护验收结果:来源变化审查与显式接受、影响集、同 ID revision、deprecation、pending reapproval、原子性、错误、`0.6.1` 兼容和 A-21 至 A-30。文末衔接已完成的 `0.8.0` 只读治理看板。
|
|
44
|
+
|
|
45
|
+
9. [13-READ-ONLY-GOVERNANCE-DASHBOARD-DESIGN.md](./13-READ-ONLY-GOVERNANCE-DASHBOARD-DESIGN.md)
|
|
46
|
+
|
|
47
|
+
记录 `0.8.0` 只读治理看板冻结设计、`0.9.0` 派生数据精简,以及 source lifecycle 后的 View Model schema 3:六个视图、HTML/JSON stdout、安全、无障碍、兼容和 A-31 至 A-38。
|
|
48
|
+
|
|
49
|
+
10. [14-FORMAL-RELEASE-READINESS.md](./14-FORMAL-RELEASE-READINESS.md)
|
|
50
|
+
|
|
51
|
+
冻结 `frontend-project-context@1.0.0` 的包边界、项目接入、CI、迁移与 A-39;source lifecycle 保持条件已关闭,public npm 已授权、完成配置并验证发布者身份。
|
|
52
|
+
|
|
53
|
+
11. [15-SOURCE-LIFECYCLE-CLOSURE-DESIGN.md](./15-SOURCE-LIFECYCLE-CLOSURE-DESIGN.md)
|
|
54
|
+
|
|
55
|
+
记录首次公开发布前来源生命周期修补的冻结设计与本地实现:处理来源删除、搬迁和永久退役,不自动接受、批准或修复;A-40 至 A-45 已通过。
|
|
56
|
+
|
|
57
|
+
## 历史证据
|
|
58
|
+
|
|
59
|
+
12. [06-HISTORICAL-PROTOTYPE.md](./06-HISTORICAL-PROTOTYPE.md)
|
|
60
|
+
13. [07-REAL-TASK-EVIDENCE.md](./07-REAL-TASK-EVIDENCE.md)
|
|
61
|
+
|
|
62
|
+
历史文档只解释为什么不再建设任务执行 Harness。它们不是程序需求、工作流或授权来源。
|
|
63
|
+
|
|
64
|
+
## Beta 证据
|
|
65
|
+
|
|
66
|
+
14. [09-B0-DTG-TMC-MOBILE.md](./09-B0-DTG-TMC-MOBILE.md)
|
|
67
|
+
|
|
68
|
+
记录首次真实项目只读接入、通用修补和同项目回归。报告中的历史“下一步”不再产生新需求。
|
|
69
|
+
|
|
70
|
+
15. [10-B0-DTG-TMC-PC.md](./10-B0-DTG-TMC-PC.md)
|
|
71
|
+
|
|
72
|
+
记录第二个真实项目只读接入和跨项目对比:核心链路与首轮通用修补再次通过。产品宪法已经停止继续寻找项目和扩充技术发现白名单。
|
|
73
|
+
|
|
74
|
+
机器状态见 [PROJECT_STATE.json](../PROJECT_STATE.json)。新窗口先读取产品宪法,再读取 [RTK.md](../RTK.md)。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 最小团队接入示例
|
|
2
|
+
|
|
3
|
+
包实际发布后,把 [package.json](./package.json) 中的脚本和 devDependency 合并到项目,并把 [project-context-check.yml](./project-context-check.yml) 复制到 `.github/workflows/project-context-check.yml`。
|
|
4
|
+
|
|
5
|
+
维护者首次初始化:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run context:init -- --id my-project --name "My Project" --write
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
团队和 CI 的只读 Gate:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run context:check
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
应提交 `.project-context/contract.json`、`.project-context/sources.lock.json`、`.project-context/projections.lock.json` 和团队明确采用的受管投影。不要提交 proposal、dashboard HTML、tarball、node_modules 或临时 Context Bundle。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": true,
|
|
3
|
+
"scripts": {
|
|
4
|
+
"context:init": "project-context init --project .",
|
|
5
|
+
"context:check": "project-context check --project .",
|
|
6
|
+
"context:dashboard": "project-context dashboard --project ."
|
|
7
|
+
},
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"frontend-project-context": "1.0.0"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Project Context
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
check:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 20
|
|
20
|
+
cache: npm
|
|
21
|
+
- run: npm ci
|
|
22
|
+
- run: npm run context:check
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frontend-project-context",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Project-local, model-neutral context governance and compilation for AI coding tools.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"docs/",
|
|
16
|
+
"examples/",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"UPGRADING.md",
|
|
19
|
+
"PROJECT_STATE.json",
|
|
20
|
+
"RTK.md",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"NOTICE"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"project-context": "./bin/project-context.mjs"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"check": "node --check bin/project-context.mjs && node --test test/project-context test/release",
|
|
29
|
+
"test": "node --test test/project-context test/release",
|
|
30
|
+
"prepack": "npm run check"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"ai-coding",
|
|
37
|
+
"context-governance",
|
|
38
|
+
"project-contract"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { canonicalJson } from "./canonical-json.mjs";
|
|
2
|
+
import {
|
|
3
|
+
sourceForContract,
|
|
4
|
+
sourceRegistrationShape,
|
|
5
|
+
sourceStatus,
|
|
6
|
+
validateContract,
|
|
7
|
+
validateProposal,
|
|
8
|
+
validateSourceLock,
|
|
9
|
+
} from "./contract-schema.mjs";
|
|
10
|
+
import { fail } from "./errors.mjs";
|
|
11
|
+
import { writeProjectState } from "./project-store.mjs";
|
|
12
|
+
import { readSourceDigest } from "./source-reader.mjs";
|
|
13
|
+
import { findConflicts, validateOverrides } from "./scope-compiler.mjs";
|
|
14
|
+
|
|
15
|
+
function sameScope(left, right) {
|
|
16
|
+
return left.kind === right.kind && (left.path ?? ".") === (right.path ?? ".");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function approval(options, timestamp) {
|
|
20
|
+
return {
|
|
21
|
+
by: options.by,
|
|
22
|
+
at: timestamp,
|
|
23
|
+
...(options.rationale !== undefined ? { rationale: options.rationale } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function preflightContract(nextContract) {
|
|
28
|
+
validateContract(nextContract);
|
|
29
|
+
const findings = [...validateOverrides(nextContract.items), ...findConflicts(nextContract.items)];
|
|
30
|
+
if (findings.length > 0) {
|
|
31
|
+
fail("approval-preflight-failed", "approval would create invalid scope overrides or unresolved contract conflicts", {
|
|
32
|
+
exitCode: 1,
|
|
33
|
+
details: { findings },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function approveProposal(root, project, proposalInput, options) {
|
|
39
|
+
const proposal = validateProposal(structuredClone(proposalInput));
|
|
40
|
+
if (proposal.projectId !== project.contract.project.id) {
|
|
41
|
+
fail("proposal-project-mismatch", "proposal belongs to a different project");
|
|
42
|
+
}
|
|
43
|
+
const ids = [...new Set(options.ids)];
|
|
44
|
+
if (ids.length !== options.ids.length) fail("approval-id-duplicate", "approval IDs must be unique");
|
|
45
|
+
const proposedById = new Map(proposal.items.map((item) => [item.id, item]));
|
|
46
|
+
for (const id of ids) {
|
|
47
|
+
if (!proposedById.has(id)) fail("proposal-id-missing", `proposal does not contain item: ${id}`);
|
|
48
|
+
}
|
|
49
|
+
const selected = ids.map((id) => proposedById.get(id));
|
|
50
|
+
const selectedSourceIds = new Set(selected.flatMap((item) => item.sources));
|
|
51
|
+
const proposalSources = new Map(proposal.sources.map((source) => [source.id, source]));
|
|
52
|
+
for (const sourceId of selectedSourceIds) {
|
|
53
|
+
const source = proposalSources.get(sourceId);
|
|
54
|
+
if (!source) fail("proposal-source-missing", `proposal source is missing: ${sourceId}`);
|
|
55
|
+
const actual = await readSourceDigest(root, source);
|
|
56
|
+
if (actual !== null && actual !== source.digest) {
|
|
57
|
+
fail("proposal-source-changed", `source changed after discovery: ${sourceId}`, {
|
|
58
|
+
exitCode: 1,
|
|
59
|
+
details: { source: sourceId, expected: source.digest, actual },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const nextContract = structuredClone(project.contract);
|
|
64
|
+
for (const sourceId of selectedSourceIds) {
|
|
65
|
+
const source = proposalSources.get(sourceId);
|
|
66
|
+
const existing = nextContract.sources.find((entry) => entry.id === sourceId);
|
|
67
|
+
if (existing && (
|
|
68
|
+
sourceStatus(existing) === "deprecated" ||
|
|
69
|
+
canonicalJson(sourceRegistrationShape(existing)) !== canonicalJson(source)
|
|
70
|
+
)) {
|
|
71
|
+
fail("source-id-conflict", `source ID conflicts with contract: ${sourceId}`);
|
|
72
|
+
}
|
|
73
|
+
if (!existing) nextContract.sources.push(sourceForContract(source, nextContract.schemaVersion));
|
|
74
|
+
}
|
|
75
|
+
const timestamp = options.at ?? new Date().toISOString();
|
|
76
|
+
for (const item of selected) {
|
|
77
|
+
const sameSubjectAndScope = nextContract.items.find(
|
|
78
|
+
(entry) => entry.id !== item.id && entry.subject === item.subject && sameScope(entry.scope, item.scope) && entry.status === "approved",
|
|
79
|
+
);
|
|
80
|
+
if (sameSubjectAndScope) {
|
|
81
|
+
fail("subject-scope-conflict", `approved item already owns subject and scope: ${item.subject}`, {
|
|
82
|
+
details: { existing: sameSubjectAndScope.id, proposed: item.id },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const approved = {
|
|
86
|
+
...structuredClone(item),
|
|
87
|
+
status: "approved",
|
|
88
|
+
approval: approval(options, timestamp),
|
|
89
|
+
};
|
|
90
|
+
const index = nextContract.items.findIndex((entry) => entry.id === approved.id);
|
|
91
|
+
if (index === -1) nextContract.items.push(approved);
|
|
92
|
+
else fail("item-id-conflict", `item ID already exists; use revise for governed replacement: ${approved.id}`);
|
|
93
|
+
}
|
|
94
|
+
nextContract.sources.sort((left, right) => left.id.localeCompare(right.id));
|
|
95
|
+
nextContract.items.sort((left, right) => left.id.localeCompare(right.id));
|
|
96
|
+
preflightContract(nextContract);
|
|
97
|
+
const confirmedSources = new Map(project.sourcesLock.sources.map((entry) => [entry.id, entry.digest]));
|
|
98
|
+
for (const sourceId of selectedSourceIds) {
|
|
99
|
+
const source = proposalSources.get(sourceId);
|
|
100
|
+
if (typeof source.digest === "string") confirmedSources.set(source.id, source.digest);
|
|
101
|
+
}
|
|
102
|
+
const nextSourcesLock = {
|
|
103
|
+
schemaVersion: 1,
|
|
104
|
+
sources: [...confirmedSources].map(([id, digest]) => ({ id, digest })).sort((left, right) => left.id.localeCompare(right.id)),
|
|
105
|
+
};
|
|
106
|
+
validateSourceLock(nextSourcesLock);
|
|
107
|
+
if (options.write) {
|
|
108
|
+
await writeProjectState(project, nextContract, nextSourcesLock);
|
|
109
|
+
}
|
|
110
|
+
return { contract: nextContract, sourcesLock: nextSourcesLock, approvedIds: ids, written: Boolean(options.write) };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function approvePendingItems(root, project, options) {
|
|
114
|
+
const ids = [...new Set(options.ids)];
|
|
115
|
+
if (ids.length !== options.ids.length) fail("approval-id-duplicate", "approval IDs must be unique");
|
|
116
|
+
const itemsById = new Map(project.contract.items.map((item) => [item.id, item]));
|
|
117
|
+
const selected = ids.map((id) => {
|
|
118
|
+
const item = itemsById.get(id);
|
|
119
|
+
if (!item) fail("item-not-found", `contract item does not exist: ${id}`, { details: { item: id } });
|
|
120
|
+
if (item.status !== "proposed") {
|
|
121
|
+
fail("item-not-pending", `contract item is not pending approval: ${id}`, {
|
|
122
|
+
details: { item: id, status: item.status },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return item;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const sourceIds = new Set(selected.flatMap((item) => [
|
|
129
|
+
...item.sources,
|
|
130
|
+
...(item.verification?.source ? [item.verification.source] : []),
|
|
131
|
+
]));
|
|
132
|
+
const sourcesById = new Map(project.contract.sources.map((source) => [source.id, source]));
|
|
133
|
+
const locksById = new Map(project.sourcesLock.sources.map((entry) => [entry.id, entry.digest]));
|
|
134
|
+
for (const sourceId of sourceIds) {
|
|
135
|
+
const source = sourcesById.get(sourceId);
|
|
136
|
+
if (!source) fail("source-reference-missing", `pending item references unknown source: ${sourceId}`);
|
|
137
|
+
if (sourceStatus(source) === "deprecated") {
|
|
138
|
+
fail("source-reference-deprecated", `pending item references deprecated source: ${sourceId}`, {
|
|
139
|
+
details: { source: sourceId },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
let actual;
|
|
143
|
+
try {
|
|
144
|
+
actual = await readSourceDigest(root, source);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
fail("pending-source-changed", `pending item source is unavailable: ${sourceId}`, {
|
|
147
|
+
exitCode: 1,
|
|
148
|
+
details: { source: sourceId, reason: error.code ?? "unreadable" },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (actual === null) continue;
|
|
152
|
+
const locked = locksById.get(sourceId);
|
|
153
|
+
if (!locked || locked !== source.digest || actual !== source.digest) {
|
|
154
|
+
fail("pending-source-changed", `pending item source is not at its accepted checkpoint: ${sourceId}`, {
|
|
155
|
+
exitCode: 1,
|
|
156
|
+
details: { source: sourceId, contract: source.digest, locked: locked ?? null, actual },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const timestamp = options.at ?? new Date().toISOString();
|
|
162
|
+
const selectedIds = new Set(ids);
|
|
163
|
+
const nextContract = structuredClone(project.contract);
|
|
164
|
+
nextContract.items = nextContract.items.map((item) => selectedIds.has(item.id)
|
|
165
|
+
? { ...item, status: "approved", approval: approval(options, timestamp) }
|
|
166
|
+
: item);
|
|
167
|
+
nextContract.items.sort((left, right) => left.id.localeCompare(right.id));
|
|
168
|
+
preflightContract(nextContract);
|
|
169
|
+
if (options.write) await writeProjectState(project, nextContract, project.sourcesLock, options.storeOptions);
|
|
170
|
+
return {
|
|
171
|
+
contract: nextContract,
|
|
172
|
+
sourcesLock: project.sourcesLock,
|
|
173
|
+
approvedIds: ids,
|
|
174
|
+
mode: "pending",
|
|
175
|
+
written: Boolean(options.write),
|
|
176
|
+
};
|
|
177
|
+
}
|