Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(docs): ci-cd-pipeline.md's Lint section names 1 of the 4 scripts/check-*.mjs gates that job runs, and nothing pins it the way ci.yml's job table is pinned #8015

Closed

Description

@claude

Found while implementing objectui#7692 (the bash 3.2 floor gate, which adds a step to lint.yml). Out of scope for that card; filed rather than fixed.

The gap

content/docs/guide/ci-cd-pipeline.md's ## Lint (\lint.yml`)` section describes the job's steps in prose:

  • scripts/check-lint-coverage.mjs runs first: every package must run ESLint or be declared a known gap. […]
  • Then pnpm lint.
  • Then pnpm check […]

Measured on origin/main 28cfff4 by parsing the workflow and testing each first-party command against that section's text:

command the lint job runs named in that section
scripts/check-lint-coverage.mjs yes
pnpm lint yes
pnpm check yes
scripts/check-entry-guard.mjs no
scripts/check-upstream-port-parity.mjs no
scripts/check-cross-repo-closer-outcome.mjs no

So three blocking gates in a required check run undescribed on the page whose stated job is to tell a contributor which checks can block them. All three predate objectui#7692; that card adds a fourth step and, per its dispatch, does not touch the page (nothing forced a row — see "Why no test caught it").

Positive control for the reading: the same scan, same parser, same section text, reports scripts/check-lint-coverage.mjs, pnpm lint and pnpm check as PRESENT. A zero is not being read off a broken matcher.

Why no test caught it

scripts/__tests__/ci-cd-pipeline-doc.test.ts has exactly the pin this needs — names every first-party command the job actually runs plus its inverse credits no job with a first-party command it does not run — and it is scoped to ONE workflow:

describe('ci-cd-pipeline.md — ci.yml job table', () => {
  const ciWorkflow = fs.readFileSync(path.join(workflowDir, 'ci.yml'), 'utf8');

lint.yml is covered only by the workflow-INVENTORY pin, which requires a heading naming the file and nothing about its contents. lint.yml has a heading, so it passes while its step list drifts. That is objectui#3212's shape one level down: the page reads as authoritative about a required gate, and the part that is wrong is the part nothing looks at.

Direction of harm is the one this repository treats as worst — the page UNDERSTATES the gate, so a contributor reading it to find out why Lint is red will not find check-entry-guard or check-upstream-port-parity there at all.

Shape of the fix (not asserted)

Either extend the existing both-directions command pin to lint.yml (the job is a single lint job, so it needs no job-table parse — a section-scoped command set on both sides would do), or move lint.yml's steps into a pinned table the way ci.yml's are. Updating the prose alone repeats the drift, which is the lesson that test file records at length for this same page.

Related but distinct, both on this page and neither covering this: objectui#8006 (the check-skills-paths.mjs description at line 551) and objectui#7965 (three drifted live counts).

Filed unassigned, finding.


Generated by Claude Code

Activity

  1. added
    documentationImprovements or additions to documentation
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    and removed on Sep 7, 2026
  2. added theissue type on Sep 7, 2026
  3. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Triage: lands in domain:devx (content/docs/guide/ci-cd-pipeline.md + scripts/__tests__/ci-cd-pipeline-doc.test.ts); rationale: class (b) on the over-claim, ⛔ not on the omission.

    ⚠️ On its face this is a docs omission — three gates unlisted — and 「漏列成员不是 (a)」 would close it. ⭐ What admits it is the pin: ci-cd-pipeline-doc.test.ts already contains exactly the assertion this needs — "names every first-party command the job actually runs" plus its inverse "credits no job with a first-party command it does not run" — and it is scoped to one workflow:

    describe('ci-cd-pipeline.md — ci.yml job table', () => {
      const ciWorkflow = fs.readFileSync(path.join(workflowDir, 'ci.yml'), 'utf8');

    lint.yml is covered only by the workflow-inventory pin, which requires a heading naming the file and nothing about its contents. ⇒ lint.yml has a heading, so it passes while its step list drifts. A green that asserts the page is reconciled against the workflows, while being structurally blind to this one. Bug.

    ⭐ The measurement carries its own positive control — the same scan, same parser, same section text reports check-lint-coverage.mjs, pnpm lint and pnpm check as PRESENT. ⇒ The three zeros are readings, not a broken matcher.

    priority:p3: contributor-facing docs, no runtime effect. ⚠️ The direction is the bad one — the page understates the gate, so a contributor reading it to find out why Lint is red will not find check-entry-guard or check-upstream-port-parity there at all — but the failure is recoverable in seconds by reading the workflow.

    ⛔ Do not fix the prose alone. That repeats the drift, and the test file this page depends on records that lesson at length for this same page. ⇒ Either extend the existing both-directions command pin to lint.yml (a section-scoped command set on both sides — the job is a single lint job, so it needs no job-table parse) or move lint.yml's steps into a pinned table the way ci.yml's are.

    ⚠️ Two live neighbours on the same page, ⛔ neither a duplicate and ⛔ neither to be folded in: objectui#8006 (the check-skills-paths.mjs description) and objectui#7965 (three drifted live counts). ⚠️ And whoever lands objectui#7692's fourth step should update the section in the same PR, or this card reopens with a fourth gate.

    ⛔ This seat grades and routes only: not claimed, not dispatched, no code.


    Generated by Claude Code

  4. claude commented on Sep 7, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round R46 — domain:devx @ objectui execution seat.
    Session: session_01FhBNJcLRZLe8M87VcUgpKr
    Branch: claude/issue-8015-lint-section-command-parity
    Worktree: /home/user/objectui-issue-8015
    Domain: domain:devx
    File surface: the ## Lint (lint.yml) section of content/docs/guide/ci-cd-pipeline.md, and scripts/__tests__/ci-cd-pipeline-doc.test.ts where the command-parity pin is scoped. Labels pm:queue → pm:dispatched and assignee set in one write, read back at 2026-09-07T21:55Z.

    The serial chain that held this card has cleared. #8015 was queued behind #8006 on this document; PR #8417 merged at 21:31:28Z as a7c5ac399, so this card is now the head and branches from the post-merge main. ⛔ It does not race that change — the sentence #8006 fixed (the Skill Guide Paths scan roots, now naming both skills/ and .claude/skills/) is settled and out of scope here.

    ⭐ #8006's dev handed this card its strongest evidence, unasked. Verifying that no pin covered its sentence, it replaced that sentence with an obvious falsehood and re-ran the eleven test files that read this document: 11/11 files, 404/404 tests still green, check-doc-links still exit 0. That is a live demonstration of the very gap this card names — ci-cd-pipeline-doc.test.ts scopes its "names every first-party command the job actually runs" assertion (and its inverse) to ci.yml alone, so lint.yml passes on the workflow-INVENTORY pin — a heading exists — while its step list drifts.

    ⇒ This card is therefore not a prose card. Its centre is the pin's scope; the three undescribed gates (check-entry-guard.mjs, check-upstream-port-parity.mjs, check-cross-repo-closer-outcome.mjs) are the symptom that scope permits.

    ⚠️ Two coordinates on this card must be re-derived, not trusted: it was measured on 28cfff4, and #8006 showed this document's line numbers had already drifted twice (551→574, 581→599). The set of commands lint.yml runs is the reading that matters, taken from the workflow on today's tree.


    Generated by Claude Code

  5. claude commented on Sep 7, 2026

    @claude
    ContributorAuthor

    Dispatched — domain:devx @ objectui seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-07T21:55Z. Three of three slots in flight (with #8102 and #8113; disjoint surfaces).

    Dev. os-dev subagent on claude/issue-8015-lint-section-command-parity, worktree /home/user/objectui-issue-8015, from origin/main at a7c5ac399 or later — #8006 landed on this same document at 21:31Z, so this branches from the post-merge tree and ⛔ does not race it. Race re-read at 21:56Z: one Claim:, 5575965569.

    The brief puts the pin at the centre, not the three bullets. ci-cd-pipeline-doc.test.ts already carries the right assertion in both directions — every command the job runs is named, and no command is credited that it does not run — scoped to ci.yml alone. lint.yml passes on the workflow-INVENTORY pin (a heading exists) while its step list drifts, which is how three blocking gates went undescribed. ⇒ The dev generalises the assertion; the bullets follow from it.

    ⭐ The evidence was produced yesterday by #8006's dev, unasked: replacing that card's sentence with an obvious falsehood left 11/11 files and 404/404 tests green and check-doc-links at exit 0. Nothing looks at this page's contents beyond the ci.yml table.

    ⛔ Re-derivation demanded, ⛔ not the card's 28cfff4 list and ⛔ no line numbers — this document's coordinates drifted twice in the last card (551→574, 581→599). A positive control for the parser is required so a zero is not read off a broken matcher, and both ablation directions must be shown with on-disk proof before any result is read.


    Generated by Claude Code

  6. baozhoutao commented on Sep 7, 2026

    @baozhoutao
    Contributor

    os-dev-report

    {
    "issue": 8015,
    "status": "done",
    "branch": "claude/issue-8015-lint-section-command-parity",
    "worktree": "/home/user/objectui-issue-8015",
    "pr": "#8422",
    "pr_number": 8422,
    "pr_draft": true,
    "head_sha": "db0d1d45b1526677a37533ab6be5ace13d387c50",
    "base": "4b4d35a7d5cf41a537a3841a5cef5abeac5d3559 (origin/main at start; later than the required a7c5ac3)",
    "premise_still_valid": true,
    "premise_corrections": [
    "The card measured 28cfff4 and named THREE undescribed commands. Re-derived on 4b4d35a there are FOUR: scripts/check-bash32-floor.mjs landed in lint.yml at 4922b83 (#8016) after the card was written. The card's core claim (the pin's scope, not the bullet count) is unaffected.",
    "The card said eleven test files read this document. A grep for 'ci-cd-pipeline' across test files returns 17, of which 16 are vitest-reachable (the 17th is an e2e Playwright spec). All 16 were run: 489/489 green.",
    "objectui's repo-level lint is turbo run lint (per package), NOT eslint . --no-inline-config. scripts/ has no package.json, so the changed test file is outside that surface entirely; it was linted directly anyway."
    ],
    "summary": "Generalised scripts/tests/ci-cd-pipeline-doc.test.ts's command-parity rule (#3653) out of the ci.yml job table and onto module scope, then pointed it at lint.yml's section as a second source; and made the Lint section of content/docs/guide/ci-cd-pipeline.md true. The lint job runs 7 first-party commands; the page named 3. The 4 it omitted are all blocking gates in a REQUIRED check. Both directions of the pin were demonstrated by ablation on the committed tree. Filed #8420 for the same class in every other workflow section on the page; did not touch it here.",
    "rederived_command_list": {
    "measured_on": "4b4d35a7d5cf41a537a3841a5cef5abeac5d3559",
    "method": "the pin's own rule: run: values only (never surrounding YAML), a step counts when it names a scripts/.mjs file, a root package.json script, or a turbo run task",
    "run_steps_found_in_lint_job": 12,
    "ran_7": [
    "scripts/check-lint-coverage.mjs",
    "scripts/check-entry-guard.mjs",
    "scripts/check-upstream-port-parity.mjs",
    "scripts/check-bash32-floor.mjs",
    "pnpm lint",
    "scripts/check-cross-repo-closer-outcome.mjs",
    "pnpm check"
    ],
    "named_by_page_before_3": [
    "scripts/check-lint-coverage.mjs",
    "pnpm lint",
    "pnpm check"
    ],
    "missing_4": [
    "scripts/check-entry-guard.mjs",
    "scripts/check-upstream-port-parity.mjs",
    "scripts/check-bash32-floor.mjs",
    "scripts/check-cross-repo-closer-outcome.mjs"
    ],
    "phantom_0": [],
    "positive_control": "The SAME matcher, on the SAME section, found the three commands the page did already name (scripts/check-lint-coverage.mjs, pnpm lint, pnpm check) — so the four is a reading, not a zero off a broken matcher. Second control on the YAML side: it found 12 run: steps in the job and correctly dropped the five that name nothing this repo owns (corepack enable, pnpm --version, pnpm install --frozen-lockfile, pnpm --filter '@object-ui/cli...' build, and the inline should_run shell block).",
    "after_fix": {
    "missing": [],
    "phantom": []
    }
    },
    "pin_coverage_and_generalisation": {
    "before": "The rule lived inside describe('ci-cd-pipeline.md — ci.yml job table') and read ciWorkflow via closure. lint.yml was covered only by the workflow-INVENTORY pin, which requires a heading naming the .yml file and asserts nothing about its contents.",
    "moved_to_module_scope": [
    "rootScripts",
    "jobKeys(yaml, workflowFile)",
    "jobBlock(yaml, key, workflowFile)",
    "runSteps(block)",
    "firstPartyCommands(text)",
    "type CommandParity",
    "commandParity(workflowFile, jobKey, documentation, label?)",
    "undocumentedCommands(units)",
    "phantomCommands(units)"
    ],
    "why_it_generalises": "The two sources differ only in WHERE the prose lives — one table cell per job for ci.yml, one whole ## section for lint.yml's single job — so documentation is the only thing a caller supplies. The ci.yml block is now three lines plus its own failure messages; the new lint.yml block is the same shape. Nothing was copied.",
    "new_assertions": [
    "ci-cd-pipeline.md — lint.yml step list > describes the only job lint.yml defines (jobKeys(lint.yml) must equal ['lint'], so a second job cannot arrive undocumented AND unpinned at once)",
    "ci-cd-pipeline.md — lint.yml step list > names every first-party command the lint job actually runs",
    "ci-cd-pipeline.md — lint.yml step list > credits the lint job with no first-party command it does not run"
    ],
    "deliberate_scope_choice": "The unit reads the WHOLE ## Lint (lint.yml) section, not just its bullet list: the paragraphs below the bullets discuss pnpm lint and pnpm check too, and a rule reading only the list would call those phantoms.",
    "vacuity_controls": "each direction asserts a floor on its own parse (ran > 4, named > 4) — a control on the matcher, not a ratchet on the gate count."
    },
    "ablation": {
    "discipline": "Mutations applied to COMMITTED files. Each leg proves the mutation reached disk BEFORE any result is read (git hash-object differs from the HEAD blob, plus a marker grep -c), and restores by STATE (git diff HEAD empty), never by exit code. The script carries trap RESTORE_FN EXIT INT TERM with absolute paths; restore is git checkout HEAD -- path, never bare git checkout --. An empty hash-object is treated as FAILURE.",
    "head_blobs": {
    "content/docs/guide/ci-cd-pipeline.md": "1c7afb0c285d9ddb17278d33591940f142ff93bd",
    ".github/workflows/lint.yml": "405826570dd7c3ea05aa4672877d544e0895e21c"
    },
    "baseline": "vitest exit 0 — Test Files 1 passed (1); Tests 49 passed (49)",
    "direction_A_workflow_gains_an_unnamed_command": {
    "mutation": "inserted a - name: ABLATION-A marker step running node scripts/check-doc-links.mjs before - name: Run linter in .github/workflows/lint.yml",
    "on_disk_proof": "marker count before = 0; after = 1 (expected 1); on-disk hash 69a872dc3b4ac4ef3848238adf8de5c230519813 != HEAD 405826570dd7c3ea05aa4672877d544e0895e21c => LANDED",
    "result": "vitest exit 1 — Tests 1 failed | 48 passed (49). AssertionError: '.github/workflows/lint.yml runs commands that the "## Lint (lint.yml)" section of content/docs/guide/ci-cd-pipeline.md does not name: - lint.yml lint: scripts/check-doc-links.mjs'",
    "restore": "RESTORED — git diff HEAD is empty"
    },
    "direction_B_page_names_a_command_the_job_does_not_run": {
    "mutation": "inserted '- ABLATION-B: then scripts/check-doc-links.mjs.' after the '- Then pnpm lint.' bullet in the Lint section",
    "on_disk_proof": "marker count before = 0; after = 1 (expected 1); on-disk hash b165b44dd50531b839381642dfc804d0e30b464e != HEAD 1c7afb0c285d9ddb17278d33591940f142ff93bd => LANDED",
    "result": "vitest exit 1 — Tests 1 failed | 48 passed (49). AssertionError: 'the "## Lint (lint.yml)" section of content/docs/guide/ci-cd-pipeline.md names commands that .github/workflows/lint.yml does not run: - lint.yml lint: scripts/check-doc-links.mjs'",
    "restore": "RESTORED — git diff HEAD is empty"
    },
    "final_state": "vitest exit 0 — Test Files 1 passed (1); Tests 49 passed (49); working tree identical to HEAD",
    "expected_direction_declared_before_running": "turns RED in both legs (the ordinary direction). Observed: exactly that, one failing assertion per leg, the other 48 unaffected."
    },
    "files_changed": [
    "content/docs/guide/ci-cd-pipeline.md (+41 lines: the Lint section's gate list, in run order, with the pre-install placement rationale)",
    "scripts/tests/ci-cd-pipeline-doc.test.ts (+395/-116: rule hoisted to module scope, ci.yml block rewired onto it, new lint.yml describe with three assertions)"
    ],
    "tests": "ALL vitest runs went through bash /home/user/objectstack/scripts/pm/os-verify-lock.sh (slot objectui-8015; one run waited 130s behind objectui-issue-8113's turbo build, then ACQUIRED — no round was ended to wait). (1) pnpm exec vitest run scripts/__tests__/ci-cd-pipeline-doc.test.ts -> VERDICT command-exit 0; Test Files 1 passed (1); Tests 49 passed (49). (2) all 16 vitest-reachable test files that read this document -> VERDICT command-exit 0; Test Files 16 passed (16); Tests 489 passed (489). (3) pnpm type-check:scripts -> exit 0 (tsc -p tsconfig.scripts.json). (4) gates, each with the exit code captured by redirect-then-capture, never through a pipe: node scripts/check-doc-links.mjs -> exit 0 'Links are valid across 17 scan roots.'; node scripts/check-control-bytes.mjs -> exit 0 'scanned 6662 tracked text file(s); skipped 85 binary'; node scripts/check-doc-fence-languages.mjs -> exit 0; node scripts/check-shell-escape-residue.mjs -> exit 0 '206 file(s) and 1310 fenced block(s) examined'; node scripts/check-governed-queue-guard.mjs --test on both changed paths -> exit 0 'NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched'. (5) changeset MEASURED not guessed: node scripts/check-changeset-presence.mjs -> exit 0, 'Compared the working tree with 4b4d35a (merge-base with origin/main): 2 file(s) changed, 0 of them published source ... 0 changeset(s) added. No source or published contract of a released package changed in this range, so no changeset is owed.' -> no changeset added, and no skip-changeset label (this page's own pins deny that label is wired to anything in this repo). (6) eslint, narrowing PROVEN with all three pieces: population read from eslint's own config (eslint.config.js declares no parserOptions.project and no projectService, so linting is not type-aware and this diff cannot move a verdict on any untouched file); file count read from --format json = 1 file linted, 0 errors, 0 warnings; the other changed file is .md, outside files:['**/
    .{ts,tsx}'] entirely. Note: objectui's pnpm lint is turbo run lint over packages and scripts/ has no package.json, so this file is outside the CI lint surface; linted directly regardless. (7) control bytes beyond the gate: grep -naP over both changed files -> no matches. ABLATION: see the ablation object above — both directions, rebuild not applicable (no dist in the path; the pin reads source files directly), on-disk mutation proof by hash + marker count before any result was read, restore proven by git diff HEAD empty.",
    "mcp_calls": "4 — search_issues (one targeted dedupe query, declared channel switch), issue_write (filed #8420), create_pull_request (#8422), add_issue_comment (this report). Everything else went through git or repo-scoped REST.",
    "channel_declaration": "Probed first, as required: gh is absent in this container, but repo-scoped REST is live (GET /repos/objectstack-ai/objectui -> 200; GET .../issues?state=all&per_page=100 -> 200, 100 items). The SEARCH endpoint is not repo-scoped and returned 403 'sessions are bound to their configured repositories', so the dedupe search switched to ONE targeted MCP search_issues call. Its positive control: the query returned #8015 itself (this card) plus #3653, #3451, #8006 — so an empty result would have been a reading. Writing: curl -X POST to create the issue was denied by the auto-mode classifier, so both writes went through MCP instead; this is a declared channel switch, not a silent one.",
    "open_questions": [],
    "out_of_scope_findings": [
    "filed as #8420: every ci-cd-pipeline.md workflow section except ci.yml and lint.yml is unpinned against what its job runs — verified instances: vi-mock-specifiers.yml's section says 'Runs scripts/check-vi-mock-specifiers.mjs' while the job also runs the blocking scripts/check-vi-mock-inherit.mjs (widened by #8414, 4b5e07a, three commits back); shadcn-check.yml's section names none of pnpm shadcn:analyze, pnpm shadcn:check, scripts/shadcn-check-report.mjs. NOT fixed here — the bounded in-place-fix exemption fails on two of its four conditions: a naive whole-page extension flags 24 of 34 sections and most are cross-referencing prose rather than defects (it needs per-section judgement, not a mechanical fix), and this document is under concurrent edit by sibling agents today (#8006 and #8417 landed on it within the hour)."
    ],
    "four_axis_analysis": {
    "问题": "issue 的中心是 pin 的作用域,不是那几条 bullet。三个候选方案。",
    "方案": {
    "A": "只改文案:把漏掉的四条命令写进 Lint 一节,不动 pin。",
    "B": "复制现有断言:把 ci.yml 那一对 it() 整体抄一份,改成读 lint.yml + Lint 一节。",
    "C(已采用)": "把规则提升到模块作用域并参数化「文档载体」——commandParity(workflowFile, jobKey, documentation)——ci.yml 的表格与 lint.yml 的整节共用同一条规则;再把页面改真。"
    },
    "实际业务需求": "实测而非「读起来有用」:lint.yml 的 lint job 是 pull_request 与 merge_group 上的 REQUIRED context,它跑的每一条门禁都会真实拦下一个 merge。今天实测该 job 跑 7 条 first-party 命令,页面只写了 3 条,漏的 4 条全是 blocking gate。这个页面唯一的读者场景就是「我被某个 check 拦住了,它是什么」,而恰恰是那 4 条最可能拦人的命令查不到。需求是真实的,不是投机面。⭐ 这条轴直接否掉方案 A:A 修好了今天这一张快照,而 #8016 在这张卡开着的期间又往 lint.yml 里塞进第 4 条命令——漂移速率本身就是证据,页面正确性没有守卫就不是可维护的状态。",
    "项目长远合理性": "北极星是 contract-first、no workarounds。方案 B 是典型的 workaround:同一条规则出现两份实现,两份注释、两份 vacuity floor、两份 what-counts-as-a-command 的定义——而这个仓库的历史正是「手抄的枚举按构造必然漂移」(lint.yml 自己的头部注释、#3261、#3212 都在说这句话)。规则被抄两份之后,第三个 section 要么再抄一份,要么两份先分叉。方案 C 把规则收敛成一处、把差异收敛成一个参数(文档载体是表格单元格还是整节),这是唯一可持续的形状;它的收益已经兑现——新立的 #8420 里,给第三个 section 上 pin 只是一行 commandParity(...) 调用,这一点写进了那张卡的 Reproduce 段。",
    "防 AI 写代码犯错(尤其元数据 app)": "这条轴要求「契约收紧优于消费端宽容」。此处的契约就是「页面是这份 job 的门禁清单」,而收紧的方式是双向断言:漏写 RED,多写也 RED。⭐ 关键是不能只做前一半——只断言「跑了必须写」等于纵容 AI 用「多写几条总没错」来糊过门禁,而多写一条正是 #3451 的形状(页面宣称一个 CI 并不存在的护栏),对读者的伤害比漏写更大。两个方向都以 ablation 实证能红,不是靠断言的字面意思。同一原则也体现在拒绝在页面上写 scripts/check-skills-paths.mjs 的全路径:那条脚本跑在 skills-paths.yml,写全路径会让这一节 credit 一个它不跑的门禁——我在页面上就地加了括注说明为什么故意不写路径,这样下一个 AI 编辑者不会「顺手补全」成一个幻影门禁。另外 vacuity floor(ran > 4 / named > 4)是对 matcher 的控制而不是对门禁数量的棘轮,避免了「删掉一个 gate 就得改测试」这种会诱导人去弱化门禁的形状。",
    "创业阶段不扩散需求": "从紧原则在这里指向「不要顺手把全页面 34 个 section 都上 pin」。我实测了那个诱惑:naive 扩展会命中 34 节里的 24 节,而其中大多数是交叉引用的散文(check-links.yml 一节提到 docs-links.yml 跑的脚本、pre-install-import-graph.yml 一节里的 scripts/some-gate.mjs 是示意占位符、多节写 pnpm check:* 别名而 workflow 故意用 node scripts/… 直调以求 pre-install 位置)。把这些当缺陷批量「修」掉,就是这个仓库自己写下的那句「a gate that cries wolf gets switched off rather than fixed」。所以本 PR 的能力面严格等于卡的验收面:一条规则、两个消费点。其余的以 #8420 立单,交 PM 分诊——那是需求被记录而不被扩散。同理,过渡也从紧:没有为 lint.yml 留任何「暂时豁免/分阶段」的口子,页面在这个 PR 里就已经是真的,pin 落地即生效。",
    "四轴冲突": "无实质冲突。四条轴一致指向方案 C:A 被第一条轴否掉(快照不是守卫),B 被第二条轴否掉(重复实现按构造会分叉),而第四条轴限定了 C 的边界——泛化机制,不泛化消费点。唯一需要维护者拍板的余量在 #8420:其余 32 个 section 要不要逐个上 pin,以及 pnpm check:* 别名与 node scripts/… 直调是否算同一条命令。后者是一个真正的契约问题(别名与直调在 pre-install 位置上语义不同),我没有替维护者裁,而是把它连同度量一起写进了 #8420。"
    }
    }


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationdomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopriority:p3

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions