Skip to content

Commit e3ef52b

Browse files
qq9340100claude
andauthored
refactor(spec): 三个热点生成物按 category / entrypoint 分片,拆掉合并队列的串行税 (#5837) (#6069)
* refactor(spec): 三个热点生成物按 category / entrypoint 分片,拆掉合并队列的串行税 (#5837) `merge=os-regen`(#4675)只在本地 git 生效;合并队列在服务端重建 PR 时不跑自定义 merge driver,所以两个都动过 `authorable-surface.json`(310KB / 7941 行排序数组)的 PR 在队列里是纯文本冲突,第二个必然被踢。内容本来就按 `<category>/<Def>[:<prop>]` 天然分组,按它拆开,动不同 category 的两个 PR 从此文件不相交。 S1 布局 + 生成器:新增 `scripts/lib/sharded-artifacts.ts`(布局、writer、完整性检查、 历史基线读取一处收口);`gen:schema` 写 `authorable-surface/` 与 `json-schema.manifest/`,`gen:api-surface` 写 `api-surface/`(`.` → `root.json`)。 S2 门禁 + 锚点:所有闸读**整个目录**当一个集合,ratchet 语义逐条不变; `authorable-surface.base.json` 保持聚合单文件(只有 `--update-base` 写它,不在 churn 路径上,且 `baseRev` 是整个 surface 的一个 commit),其比对改读基线 commit 的分片并 聚合,authenticity 判据两半未动。 S3 消费方:build-docs / build-spec-changes / root-index.test / release-spec-changes.sh / check-published-files / package.json `files` 全量改点。 S4 收尾:`.gitattributes` 与 `regen-artifacts.mjs` 路由到分片路径,单体文件退役, AGENTS.md / 两个内部 skill / lint.yml 注释 / ADR-0059 与 ADR-0087 路径修订、 `docs/spec-generated-artifact-sharding.md`。 新增 `scripts/sharded-artifacts.test.ts`(23 例):局部性与幂等各成常驻断言 —— 「只改 category X → 只有 X.json 变化,其余分片逐字节不变」,以及「同一 key 集合重写 零文件」。#5976 的 def key 撞名守卫按剥后缀 schema 名判定,与输出路径无关,其反向 验证测试同步改为断言分片目录名(否则该断言会变成空跑)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY * docs(spec): #6000 引入的活文档同步到分片路径 (#5837) merge 带进来的 #4723/#6000 新增了几处描述「check:docs 曾重写两个 tracked 投影」的散文, 它们指的是当前机制而不是历史叙述,所以路径随分片一起更新:build-docs.ts、AGENTS.md、 check-regen-pending.mjs、check-generated-ledger.test.ts、schema-tree-freshness.test.ts。 另外 build-schemas.ts 里把 api-surface 说成「另一个见证者」的两处、以及 migrations/spec-changes.ts 的 Release 工作流描述,同属活文档。 ⛔ 有意不动的:所有 `src/**` 与测试里追述「当年这个 key 怎么出账」的历史叙述 (http-server.zod.ts / theme.zod.ts / data-engine.zod.ts / retry-policy.* / …), 以及 ADR 正文 —— 后者按 AGENTS.md #13 用顶部 Amended 行声明路径迁移,不改写决策记录。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY * chore: 重触发 CI —— 平台冻结窗口的 rerun attempt 卡死预排队,换 head 重新调度 20:01Z 平台冻结窗口里挂的 rerun attempt(补 Dogfood Verify CLI + 聚合门)卡在 GitHub 预排队 limbo:409 不可取消,近 4 小时零 job 实例化。调度层已恢复但只对 新触发生效,所以换一个 head sha 重新调度整轮门禁。 空提交:零文件变化,不 merge main,不 rebase。aa121e7 上已 success 的门禁 (ESLint / TypeScript Type Check / Test Core 1-3 / Dogfood 1-3 / Temporal / Check Changeset)在新 head 上原样重跑。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 820ea5d commit e3ef52b

80 files changed

Lines changed: 15825 additions & 14323 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
refactor(spec): 三个热点生成物按 category / entrypoint 分片,合并队列的串行税消失 (#5837)
6+
7+
`merge=os-regen`(#4675)只在**本地** git 生效。合并队列在服务端重建 PR 时不跑自定义
8+
merge driver,所以两个都动过 `authorable-surface.json`(310KB、7941 行排序数组,每个
9+
spec PR 都会重写)的 PR 在队列里是纯文本冲突,第二个必然被踢——spec 车道一次只能放行
10+
一单。driver 自己的注释里记着这笔税的形状:一个下午 4 次合并 9 处冲突,****真语义
11+
冲突,全部是集合并集。付的是「单体文件」这个形状的钱,不是分歧的钱。
12+
13+
**布局变化(发布产物路径,见下方档位说明)**
14+
15+
| 之前 | 现在 | 分片键 |
16+
|:---|:---|:---|
17+
| `packages/spec/authorable-surface.json` | `packages/spec/authorable-surface/< category >.json` | def key 的 category 段 |
18+
| `packages/spec/json-schema.manifest.json` | `packages/spec/json-schema.manifest/< category >.json` | 同上 |
19+
| `packages/spec/api-surface.json` | `packages/spec/api-surface/< entry >.json` | 已发布入口(`.``root.json`) |
20+
21+
有意维持单体:`spec-changes.json`(按版本键控)、`api-surface-signatures.json`(1.3KB)、
22+
`authorable-surface.base.json`(只有显式 `--update-base` 会写,从不在 churn 路径上,且
23+
它的 `baseRev` 是整个 surface 的**一个** commit——分片会让不同分片镜像不同 revision,
24+
那是任何上游 commit 都没有过的状态)。
25+
26+
**ratchet 语义逐条不变。** 所有门禁读**整个目录**当作一个集合,而不是「这次构建会写的
27+
那些分片」:删掉一整个分片文件 = 删掉它的 key,checks (a)/(c) 看到的缺失 key 与从前删
28+
单体文件里的行时一模一样;没人重生成的分片报**陈旧**而不是被跳过;#4662 的逐字节规范
29+
形式比对现在是逐分片做的,手改因此还能被**指名到文件**#5976 的 def key 撞名守卫不受
30+
影响——它按剥后缀的 schema 名判定(`shared/HttpMethod`),与输出路径无关,且仍在两个
31+
ratchet 之前运行。
32+
33+
**对消费者的可见影响,以及为什么是 patch。** `@objectstack/spec``files`
34+
`api-surface.json` 改成了 `api-surface`,所以 npm 包内该快照的路径变了(它不在
35+
`exports` 里,不是可 import 的子路径,是给工具读的文件)。导出面本身**零变化**
36+
(`check:api-surface` 实测 0 breaking / 0 added),运行时行为、类型、schema 一律未动,
37+
因此不是 major;仓内唯一的读点(release 工作流的 surface diff)随之更新,并且会按上游
38+
tarball 实际携带的形状读取——`api-surface/` 目录(本次起)、`api-surface.json` 单文件
39+
(protocol 15 起至本次)、两者皆无(protocol 15 之前)。外部若有直接读
40+
`node_modules/@objectstack/spec/api-surface.json` 的工具,改读 `api-surface/` 目录并把
41+
各分片的 `exports``entry` 合并即可,内容逐条相同。

.claude/skills/pm-dispatch/SKILL.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,9 +1516,10 @@ file-disjoint;它管不到**先后两单都碰 `packages/spec` 生成物**的情
15161516

15171517
```
15181518
packages/spec/spec-changes.json
1519-
packages/spec/authorable-surface.json
1520-
packages/spec/json-schema.manifest.json
1521-
packages/spec/api-surface.json
1519+
packages/spec/authorable-surface/**
1520+
packages/spec/authorable-surface.base.json
1521+
packages/spec/json-schema.manifest/**
1522+
packages/spec/api-surface/**
15221523
packages/spec/api-surface-signatures.json
15231524
docs/protocol-upgrade-guide.md
15241525
docs/audits/2026-07-unknown-key-strictness-ledger.counts.md
@@ -1562,7 +1563,7 @@ git grep "<上一单实现体符号>" origin/main -- <实现文件> # 实
15621563

15631564
> 断言 `pnpm --filter @objectstack/spec check:authorable-surface` **绿**即可。锚点
15641565
> authenticity 的定义是两件事:`baseRev``origin/main`**祖先**,且它记录的 keys
1565-
> **该 commit**`authorable-surface.json` 逐行一致(`verifyCommittedSurfaceBase`
1566+
> **该 commit** 的 authorable surface 逐行一致(`verifyCommittedSurfaceBase`
15661567
> 就查这两条)。`baseRev` **允许滞后** —— `gen:schema` 只在 keys 真的漂移时才重写它
15671568
> (在 `main` 上 merge base 就是 HEAD,该文件**必然**落后自己的 surface 一个 PR),
15681569
> 滞后只打一行 `ℹ️`,不是错误。⛔ 禁止为了凑「相等」手改锚点文件 —— 那正是 #4650

.claude/skills/spec-property-retirement/SKILL.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ Note template for a tombstone entry (verbatim house style, e.g.
147147
- 把「整 def 删除」的零变化**判成正常** → 放过一个**根本没真正删掉**的 def。
148148

149149
所以验收顺序是:**先确定路线,再决定该期待什么读数**,不要反过来用读数去猜路线。
150-
整 def 删除还有一条自证信号:`json-schema.manifest.json` 的 ratchet(#2978)会先开火,
150+
整 def 删除还有一条自证信号:`json-schema.manifest/`(#5837 起按 category 分片)的
151+
ratchet(#2978)会先开火,
151152
要求你**有意删除**对应的 manifest key;删完重跑,per-key ratchet 会自行判定为 #4650
152153
路径 3(`def no longer emitted by this build`)。这串输出本身就是路线的证据,留在 PR 里。
153154

@@ -190,7 +191,7 @@ follows. Write both.
190191
reference untouched when nothing matched.
191192
- [ ] **A `RETIRED_KEYS_BY_MAJOR` entry** in
192193
`packages/spec/src/migrations/registry.ts` — the literal
193-
`'<defKey>:<name>'` as `authorable-surface.json` spells it, minus the
194+
`'<defKey>:<name>'` as `authorable-surface/<category>.json` spells it, minus the
194195
`[RETIRED]` mark, under this major. This is the string gate (b) reads, by
195196
exact set membership; nothing is inferred and nothing radiates from a
196197
neighbouring key. The gate's failure prints the line to paste. ⚠ Do **not**
@@ -257,7 +258,7 @@ Work top to bottom; each line has a gate behind it.
257258
consumer goes with it (`PerformanceConfigSchema`, `AIKnowledgeSchema`,
258259
`ToolCategorySchema`). An exported schema with no consumer is read as a
259260
capability by whoever finds it (#3950 precedent). This — and *only* this —
260-
moves `api-surface.json`: that snapshot prints type *references*, not
261+
moves `api-surface/`: that snapshot prints type *references*, not
261262
expanded shapes, so it is blind to key-level narrowing (#3883 removed three
262263
keys from `defineAction`'s input and the snapshot did not change). Its gate
263264
also lives in a different workflow (`TypeScript Type Check`, not
@@ -268,9 +269,12 @@ Work top to bottom; each line has a gate behind it.
268269
once; regenerate the counts with the python snippet in the README rather
269270
than hand-editing).
270271
- [ ] **Generated baselines**`pnpm --filter @objectstack/spec gen:schema`
271-
moves `authorable-surface.json` (tombstone → a new `… [RETIRED]` line;
272-
strict removal → the line **vanishes**, which is gate (a)'s trip wire, so
273-
delete it in the same PR deliberately) and `json-schema.manifest.json`.
272+
moves `authorable-surface/<category>.json` (tombstone → a new
273+
`… [RETIRED]` line; strict removal → the line **vanishes**, which is gate
274+
(a)'s trip wire, so delete it in the same PR deliberately) and
275+
`json-schema.manifest/<category>.json`. Both are sharded by category since
276+
#5837 — the gates read the whole directory as one set, so the retirement
277+
procedure is unchanged; only which file the line lives in moved.
274278
Then `gen:spec-changes`, `gen:upgrade-guide`, `gen:api-surface`,
275279
`gen:docs`. See AGENTS.md for the you-changed-X → regenerate-Y table.
276280
- [ ] **Forms** — prune the `{ field: '<key>' }` input from

.gitattributes

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@
66
# two PRs each add or drop a few lines the result is a set union — fully
77
# composable — but a three-way TEXT merge reports it as a conflict a human must
88
# resolve by hand. The correct resolution is always the same: discard both sides
9-
# and re-run the generator. `authorable-surface.json` alone is a 8k-line sorted
10-
# array, so any two PRs landing near each other collide.
9+
# and re-run the generator.
10+
#
11+
# ⚠️ The driver is a LOCAL git facility, and that bounds what it can fix (#5837).
12+
# The GitHub merge queue rebuilds each PR server-side, where no custom merge
13+
# driver runs — so for the three hottest artifacts a driver could not stop the
14+
# queue from evicting the second PR that touched them. Those three are now
15+
# SHARDED, one file per category / entry point, so PRs touching different
16+
# categories touch disjoint files and the queue has nothing to conflict on. They
17+
# stay routed here as directories: the driver still owns a same-category
18+
# collision, which is the residue sharding cannot remove.
1119
#
1220
# `merge=os-regen` hands those paths to `scripts/git-merge-regen.mjs`, which does
1321
# NOT text-merge them. See that file for why it also does not regenerate them
@@ -36,10 +44,10 @@
3644
# ledger's prose is hand-written and must never be resolved by regenerating.
3745

3846
packages/spec/spec-changes.json merge=os-regen
39-
packages/spec/authorable-surface.json merge=os-regen
47+
packages/spec/authorable-surface/** merge=os-regen
4048
packages/spec/authorable-surface.base.json merge=os-regen
41-
packages/spec/json-schema.manifest.json merge=os-regen
42-
packages/spec/api-surface.json merge=os-regen
49+
packages/spec/json-schema.manifest/** merge=os-regen
50+
packages/spec/api-surface/** merge=os-regen
4351
packages/spec/api-surface-signatures.json merge=os-regen
4452
docs/protocol-upgrade-guide.md merge=os-regen
4553
docs/audits/2026-07-unknown-key-strictness-ledger.counts.md merge=os-regen

.github/workflows/lint.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ jobs:
575575
# generated page and the filter kept watching the old path, so hand-edits
576576
# went unchecked for months; #3855 listed specific spec paths but no schema
577577
# dirs, so `check:authorable-surface` went dormant on exactly the PRs that
578-
# remove an authorable key; and `json-schema.manifest.json` — the #2978
578+
# remove an authorable key; and the json-schema manifest — the #2978
579579
# ratchet that is the ONLY durable record of every emitted schema, since
580580
# json-schema/ is gitignored — was never watched at all, so a PR retiring a
581581
# key from it skipped its own verifier. Six gates had already escaped to
@@ -606,7 +606,7 @@ jobs:
606606
run: pnpm --filter @objectstack/spec check:upgrade-guide
607607

608608
# The authorable KEY surface — what a metadata author may write, which for
609-
# this platform is the third-party API. `api-surface.json` records exported
609+
# this platform is the third-party API. `api-surface/` records exported
610610
# names and `api-surface-signatures.json` hashes factory types as TypeScript
611611
# PRINTS them (a reference, never structurally expanded), so neither sees a
612612
# key added to or removed from a schema. #3883 removed three authorable keys
@@ -739,7 +739,7 @@ jobs:
739739
- name: Check @objectstack/spec public API surface
740740
run: pnpm --filter @objectstack/spec run check:api-surface
741741

742-
# Same surface, the other axis: api-surface.json records that an export
742+
# Same surface, the other axis: api-surface/ records that an export
743743
# EXISTS, never what it resolves to — so four exported types sat at `any`
744744
# across a whole major with every gate green (#4171). #4115 tells consumers
745745
# to replace a local declaration with the spec import, which for those four
@@ -752,7 +752,7 @@ jobs:
752752
- name: Check no exported spec type resolves to `any`
753753
run: pnpm --filter @objectstack/spec run check:exported-any
754754

755-
# Third axis on the same surface: api-surface.json shows a name on two
755+
# Third axis on the same surface: api-surface/ shows a name on two
756756
# entries but not whether the two are ONE declaration re-exported (fine)
757757
# or TWO declarations sharing a name — the #4411 trap, where which type a
758758
# consumer gets depends on nothing but the import path and the copy that

AGENTS.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,28 @@ Even inside your own worktree, operate defensively:
251251
11. **Generated artifacts don't text-merge — a driver defers them and
252252
`pre-commit` collects the debt.** §10's "never trust git's textual merge of a
253253
generated file" is now mechanical (#4675). `.gitattributes` routes the
254-
generator-owned artifacts (`spec-changes.json`, `authorable-surface.json`,
255-
`api-surface*.json`, `json-schema.manifest.json`,
254+
generator-owned artifacts (`spec-changes.json`, `authorable-surface/**`,
255+
`authorable-surface.base.json`, `api-surface/**`,
256+
`api-surface-signatures.json`, `json-schema.manifest/**`,
256257
`docs/protocol-upgrade-guide.md`, `content/docs/references/**`) to
257258
`merge=os-regen`, so a merge that used to stop on conflicts across all of
258259
them now stops only on the hand-written files that actually need you.
259260

261+
**The driver is a LOCAL facility, and #5837 is where that bound showed.** The
262+
GitHub merge queue rebuilds each PR server-side, where no custom merge driver
263+
runs — so two PRs that both touched `authorable-surface.json` (a 310KB sorted
264+
array every spec PR rewrites) were a plain textual conflict there and the
265+
second was evicted, capping the spec lane at one PR at a time. The three
266+
hottest artifacts are therefore **sharded**: `authorable-surface/<category>.json`,
267+
`json-schema.manifest/<category>.json`, `api-surface/<entry>.json`. PRs
268+
touching different categories now touch disjoint files, and the driver keeps
269+
the residue (two PRs in the same category). Every gate reads the whole
270+
directory as one set, so the ratchet semantics are unchanged — see
271+
`packages/spec/scripts/lib/sharded-artifacts.ts`. Deliberately still single
272+
files: `spec-changes.json` (keyed by version), `api-surface-signatures.json`
273+
(1.3KB) and `authorable-surface.base.json` (written only by an explicit
274+
`--update-base`, so never on the churn path).
275+
260276
The driver does **not** regenerate. Git runs merge drivers *while* it merges,
261277
in index order, so the worktree still holds pre-merge sources — a generator
262278
run there would describe a half-merged tree and write a confidently wrong
@@ -382,7 +398,7 @@ regenerate up front:
382398
| The react-blocks contract | `check:react-blocks` | `gen:react-blocks` |
383399

384400
A `.describe()` string counts — it is not "just a comment", it lands in
385-
`content/docs/references/`. Adding one export counts — it lands in `api-surface.json`.
401+
`content/docs/references/`. Adding one export counts — it lands in `api-surface/`.
386402
Both were learned the hard way in #4040: two separate red builds, neither a logic error.
387403

388404
Don't match by hand — one command runs **every** gate and reports **all** stale
@@ -401,7 +417,7 @@ regenerate those.
401417

402418
**No `check:` script regenerates anything — that is the point of the split, not an
403419
oversight.** `check:docs` used to begin with `pnpm gen:schema`, which rewrites two
404-
*tracked* files (`json-schema.manifest.json`, `authorable-surface.json`) whenever they
420+
*tracked* files (`json-schema.manifest/`, `authorable-surface/`) whenever they
405421
are behind: running the gate edited your working tree and reported nothing, so a
406422
`check:generated` run on a stale manifest printed a red `check:authorable-surface`
407423
over a file the gate two lines below had already quietly fixed (#4711, #4723). The
@@ -434,7 +450,7 @@ inline when that gate is the one failing.)
434450
pure checks with no generator — a failure there is a real finding to fix, not an artifact
435451
to regenerate. `check:generated` names them as deliberately not run, so its "all up to
436452
date" never reads as "everything passed". The last one asks the third question about the
437-
export surface (#4446): `api-surface.json` shows a name on two entries but not whether
453+
export surface (#4446): `api-surface/` shows a name on two entries but not whether
438454
that is one declaration re-exported (fine) or two declarations sharing a name — the #4411
439455
trap, judged by symbol identity against the built dist, with the accepted cases in the
440456
shrink-only `dual-source-exports.baseline.json` (hand-edited under review, never
@@ -468,7 +484,7 @@ re-adding a skip.
468484

469485
`check:exported-any` is the one of those that also reads the built `dist/*.d.ts`, so the
470486
stale-`dist` caveat above applies to it too. It asks the other half of the
471-
`api-surface.json` question: that snapshot records an export *exists*, never what it
487+
`api-surface/` question: that snapshot records an export *exists*, never what it
472488
*resolves to*, which is how five exported symbols sat at `any` for a whole major with
473489
every gate green (#4171). A recursive Zod schema needs an annotation to break its
474490
circular inference, and `z.ZodType<any>` compiles, validates correctly, and silently
@@ -746,7 +762,7 @@ it to `OPEN_CAPABILITY_REGISTRIES` in the same PR that fixes it.
746762
working tree.
747763
3. **Add a changeset for feature work.** When the change is a feature or functional improvement, run `pnpm changeset` (or add a `.changeset/*.md` entry) describing it before committing. Pure bug fixes do **not** require a changeset.
748764
**Breaking changesets must carry their migration.** If the change removes or renames anything an author can write (a spec key, an export, a config field), the changeset body must state the FROM → TO mapping and the one-line fix — this text ships to consumers as `CHANGELOG.md` inside the npm package and is what an upgrading agent greps after the tombstone error. Removing an authorable spec key also requires a tombstone so the rejection itself carries the prescription — `retiredKey()` (`packages/spec/src/shared/retired-key.ts`) on a non-strict schema, or an entry in the relevant `UNKNOWN_KEY_GUIDANCE` / `*_RETIRED_KEY_GUIDANCE` map (see `object.zod.ts`, `ai/tool.zod.ts`) when the schema is `.strict()`. The changeset is one of fourteen surfaces a retirement touches — follow the `spec-property-retirement` skill (`.claude/skills/`) rather than reconstructing the kit, and note the two routes imply **opposite** liveness-ledger dispositions.
749-
4. **Added or removed a `packages/spec` export? Run `pnpm --filter @objectstack/spec gen:api-surface` and commit the result.** The `TypeScript Type Check` job diffs spec's built export surface against `api-surface.json`; a new export makes the snapshot stale and turns the job red. It reads the **built `dist` declarations**, so `OS_SKIP_DTS=1` — the flag you reach for to make local builds fast — skips exactly the artifact the gate inspects, and the check passes locally while failing in CI. Same shape for the other generated-artifact gates in that job (`check:docs`, `check:skill-refs`, `check:react-blocks`), which read `src/` and so do reproduce locally.
765+
4. **Added or removed a `packages/spec` export? Run `pnpm --filter @objectstack/spec gen:api-surface` and commit the result.** The `TypeScript Type Check` job diffs spec's built export surface against `api-surface/` (one shard per entry point since #5837); a new export makes the snapshot stale and turns the job red. It reads the **built `dist` declarations**, so `OS_SKIP_DTS=1` — the flag you reach for to make local builds fast — skips exactly the artifact the gate inspects, and the check passes locally while failing in CI. Same shape for the other generated-artifact gates in that job (`check:docs`, `check:skill-refs`, `check:react-blocks`), which read `src/` and so do reproduce locally.
750766
5. Update `CHANGELOG.md` / `ROADMAP.md` if user-facing or architectural.
751767
6. **Delete temporary artifacts** — screenshots, traces, scratch logs, `.playwright-mcp/`, throwaway `tmp*.ts`, ad-hoc scripts. Repo must look identical to before, minus intended changes.
752768

0 commit comments

Comments
 (0)