You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding: check-changeset-presence counts only a package's own src/ subtree, so apps/console/index.html — shipped and executable — can change with no changeset required #5733
Observation-class finding, noticed while working #5660 (PR #5732). Not fixed there — that PR declares a changeset anyway, because it also touches files under apps/console/src/.
What was measured
scripts/check-changeset-presence.mjs:416 decides whether a changed file counts as package source with:
if(path.startsWith(`${directory}/src/`)){
So the gate's population is each package directory plus /src/. apps/console/index.html is not under src/, and the gate's own summary line confirms it does not count — on PR #5732, which changed four files including index.html:
Compared the working tree with d8afbe519 (merge-base with origin/main): 3 file(s) changed,
2 of them under the src/ of a package the release covers, 0 under a package changesets
ignores, 1 changeset(s) added.
Three files, two counted — main.tsx and the test. index.html is not in the count.
Why that surface is worth a second look
apps/console/index.html is not an incidental config file. It is the console's HTML entry, it ships in the package's built dist, and it carries two inline classic scripts that run in every user's browser during parse — the pre-boot branding/runtime/config script and the crypto.randomUUID shim for insecure origins. Both are graded by tests precisely because they are shipped behaviour (src/__tests__/runtimeConfigBootDedup.test.ts and src/__tests__/insecure-origin-crypto.test.ts each extract and execute the real script text).
A PR editing only that file would change shipped console behaviour while the changeset gate stays silent.
What this is not
The src/-only scope looks deliberate for the case the script documents at :87 — a package.json dependency bump should not demand a changeset. The header does not discuss HTML entry points, so whether index.html was considered and excluded, or simply never came up, is not recorded. That is the open question; this is not an assertion that the gate is wrong.
If it is intentional, the rationale is worth a sentence in the script header, since the file it lets through is executable shipped code.
Search-before-file caveat
search_issues was returning total_count: 0 for every query during this session, including a control term known to exist — so those zero-hit searches are not readings. (The same degradation is why #5660 itself was filed by the PM on behalf of a dev agent whose search step was blocked.) list_issues worked; I scanned the titles of the 100 most recently updated open issues (of 226) and found nothing covering this. Coverage of the older 126 is therefore unverified — close as a duplicate without ceremony if one exists.
changed the title [-]finding: check-changeset-presence's population is `<pkg>/src/**` only, so `apps/console/index.html` — shipped and executable — can change with no changeset required[/-][+]finding: check-changeset-presence counts only a package's own `src/` subtree, so `apps/console/index.html` — shipped and executable — can change with no changeset required[/+]on Aug 23, 2026
Triage (first touch): pm:queue + domain:devx. The finding label is removed in the same stroke — grading a finding means leaving the finding state, and a card carrying both would be counted as still-untriaged by the bare-label reading.
Landing: scripts/check-changeset-presence.mjs:416 — the path.startsWith(\${directory}/src/`)` population test. A gate's own source, so the engineering stream.
Graded pm:queue rather than left as an observation: the body carries the line number, the gate's own summary output as evidence, and a concrete shipped example (apps/console/index.html on PR #5732) where the hole was exercised. Nothing is open to ask.
objectui three-stream split (maintainer 2026-08-21). Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」 · 「继续 批 2 批 3」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat may re-grade.
Claimed by the domain:devx @ objectui execution seat (#5748), round R11, PM session session_019b5UBNMtTzKbVtZZGvFuxe. Branch: claude/issue-5733-changeset-presence-population.
Triage graded this pm:queue with no open question: the body carries the line number (scripts/check-changeset-presence.mjs:416, the path.startsWith(\${directory}/src/`) population test), the gate's own output as evidence, and a shipped example (apps/console/index.html` on PR #5732) where the hole was exercised.
Re-derive before you change anything
That line number is from 2026-08-23 and this file has been edited since — several cards this round touched the changeset gates. Locate the population test by its code, not by its line number, and report where it actually is. Stale line numbers have already cost this lane a round today.
Also re-verify the premise itself: that a shipped, executable file outside a package's src/ can change with no changeset required. Show it as a reading on current origin/main, with the gate's own summary line.
The judgment this card actually turns on
The fix is not "scan more files" — it is deciding what the gate's population should be, and that choice has a wrong answer in each direction:
Too narrow (today): shipped executable code changes with no changeset, so consumers get a release that doesn't mention it.
Too wide: every incidental file in a package directory demands a changeset, the gate becomes noise, and people start writing empty-frontmatter changesets to silence it — which is how a gate stops being read.
Pick the population by what is published and executable, and state the rule you implemented in the file, in prose a future reader can check against. ⚠️ If the honest answer is that the boundary needs a convention the repo has not settled, say so and report rather than inventing one — that would be a maintainer call.
Verification this card owes
Non-vacuity, executable. A gate this shape passes trivially if the population is empty or the matcher is wrong:
positive — a change to a file that should demand a changeset, without one, goes red naming that file;
negative control 1 — the same change with a changeset goes green;
negative control 2 — a change to a genuinely non-shipped file (a test fixture, a local script) stays green, so the widening did not become a blanket.
Reverse-verify against the card's own shipped example: apps/console/index.html. Predict the direction before running, prove the mutation reached disk by grepping the specific text, show the gate flip, restore with git checkout HEAD -- <path> (⛔ not the bare form — it reads the mutation back and exits 0, objectstack#11648), and prove restoration with an empty git diff HEAD.
⚠️Do not let this PR trip its own gate into an infinite regress — if widening the population now demands a changeset for your own diff, that is the gate working; add the changeset its verdict asks for.
scripts/__tests__/ suite from the repo root, not package-scoped.
⛔ Out of scope: check-changeset-fixed, check-changeset-no-major, the lane job's mirror of readChangesetState (#5775 owns that — do not touch it, another seat may claim it), and the release workflow. ⛔ Do not weaken any gate to make a count come out even.
⛔ Never git stash — shared stack, other agents are using it now.
{
"issue": 5733,
"status": "done",
"branch": "claude/issue-5733-changeset-presence-population",
"pr": "https://github.com/objectstack-ai/objectui/pull/6073",
"premise_still_valid": true,
"summary": "The card's line 416 is stale; the population test was at scripts/check-changeset-presence.mjs:467 on a1c41c516, inside classifyChangedPaths, as `path.startsWith(`${directory}/src/`)`. Premise re-verified on current origin/main: appending one comment to apps/console/index.html and running the unmodified gate gave `1 file(s) changed, 0 of them under the src/ of a package the release covers` and exit 0. Widened the population to a package's PUBLISHED, EXECUTABLE source via a new exported `isPublishedSource`: (a) under PKG/src/ (unchanged, still all-of-it, answered first so nothing guarded before is guarded less), (b) PKG/index.html the bundler's HTML entry, (c) anything the package's own package.json `files` list publishes verbatim — minus documentation and licences (*.md, LICENSE*), which ship but are not behaviour. The rule is stated in the file header in checkable prose, alongside the boundaries deliberately left out (PKG/public/, PKG/*-preview.html, PKG/demo/, package.json) with the reasoning for each. Measured over every tracked file: 3330 guarded, and the widening adds exactly three — apps/console/index.html, apps/console/plugin.ts, packages/runner/index.html. No convention was invented: `files` is npm's own published-surface declaration and index.html is Vite's documented default build entry, and both packages with a root index.html do build it. The one arguable line is PKG/public/ (published verbatim by Vite, but static, not code) — excluded and argued in the header rather than left silent, flagged in open_questions for the maintainer.",
"tests": "All at final commit 50365b547, run from the repo ROOT. (1) scripts/__tests__/check-changeset-presence.test.ts: `Test Files 1 passed (1)` / `Tests 38 passed (38)` (7 new). (2) Whole scripts/__tests__/ suite: `Test Files 64 passed (64)` / `Tests 1770 passed (1770)`. (3) NON-VACUITY, all legs executable — positive: fixture change to apps/console/index.html with no changeset goes red, exit 1, output names `apps/console/index.html` under `@fixture/console`; negative control 1: same change plus a changeset goes green with `declares 1 changeset`; negative control 2: four non-shipped neighbours in the SAME package directory (public/manifest.json, preview-gallery.html, README.md, vitest.config.ts) stay green with `4 file(s) changed, 0 of them published source`. Plus a clause table over isPublishedSource, a clause-(c) leg on plugin.ts, a leg proving a `files`-published README.md still does not count, and a real-tree leg pinning three-files-and-no-fourth. (4) ABLATION proving the new tests can fail: isPublishedSource reverted to `return relative.startsWith('src/')`. NO BUILD IS INVOLVED — the test imports ../check-changeset-presence.mjs directly by relative path, so there is no dist/ leg to rebuild; the mutation was confirmed on disk before reading any result (`grep -c ABLATION-5733` = 1 injected, `grep -c 'relative === HTML_ENTRY'` = 0 removed), not from the editor's exit code. Result: `Test Files 1 failed (1)` / `Tests 5 failed | 33 passed (38)`, the five being POSITIVE, NEGATIVE CONTROL 1, the clause-(c) leg, the clause table, and the real-tree leg. The two new tests that stayed green are both stays-green assertions, which is the correct direction for them, and their branches are covered by the clause table that did go red. Restored by an `EXIT INT TERM` trap and verified byte-identical with `diff -q`. (5) REVERSE-VERIFICATION on apps/console/index.html, direction predicted before running (red, naming the file): observed `2 file(s) changed, 1 of them published source of a package the release covers` then `1 source file(s) of 1 released package(s) changed, and this change adds no changeset:` / `@object-ui/console` / `apps/console/index.html`, exit 1. Mutation proven on disk (grep hit = 1, 9460 -> 9505 bytes); restored with `git checkout HEAD -- apps/console/index.html` (NOT the bare form); restoration proven by empty `git diff HEAD` for the path, zero probe hits, bytes back to 9460, and `git status --porcelain` showing only the intended edit. Same legs run for apps/console/plugin.ts (red naming it) and for five real non-shipped files at once (green). (6) GATES, each quoting its own verdict line: check-changeset-presence.mjs on this PR's own diff `No source of a released package changed in this range, so no changeset is owed.` (exit 0 — no infinite regress); check-changeset-fixed.mjs `All workspace packages are in the changeset fixed group.`; check-changeset-no-major.mjs `No changeset declares a major bump.`; pnpm check:control-bytes `check-control-bytes: OK (scanned 5016 tracked text file(s); skipped 85 binary).`; check-lint-coverage.mjs `lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).`; check-type-check-coverage.mjs `type-check coverage: 45/46 via type-check, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.`; pnpm type-check:scripts exit 0 with no diagnostics; eslint --no-inline-config --format json on both changed files reports 2 files, 0 errors, 0 warnings. Exit codes were captured by redirect-then-capture, never after a pipe. On eslint scope: this is NOT a narrowing — root `pnpm lint` is `turbo run lint`, a per-package fan-out, and scripts/ is not a package, so pnpm lint reaches neither changed file; the targeted run uses the root eslint.config.js and its own --format json count of 2 is the complete lint population for this diff. node-esm-load-gate.yml is a nightly/dispatch workflow requiring a real build, not a per-PR gate for this diff; it is red in this unbuilt worktree naming `@object-ui/console: no-build-output — ./plugin.js does not exist after the build`, a file this diff does not touch. CI convergence is the PM's to read.",
"open_questions": [
{
"question": "Should PKG/public/ join the guarded population? It is published verbatim (Vite copies it into dist/, so apps/console/public/manifest.json reaches every user) but is not executable, so it fell on the documentation side of the published-AND-executable line.",
"options": [
"A - leave it out, as implemented: static assets sit with the READMEs; a favicon or logo swap demanding a declaration is exactly the friction that trains authors to answer this gate with an empty changeset they did not read",
"B - guard it: manifest.json changes the installed PWA's name and display mode, which is user-visible even though no code runs"
],
"recommendation": "A, because the population's failure mode in the wide direction is the gate stopping being read at all, and manifest.json is one file against four static assets in the only public/ a released package has. The exclusion is argued in the gate header as a considered boundary rather than a silence, so moving to B later is a one-clause edit with the reasoning already on the page. Not blocking: this is flagged for the maintainer, not a needs_decision."
}
],
"out_of_scope_findings": []
}
Re-post of the report in #issuecomment-5397616781, unchanged. That comment's leading HTML-comment marker did not survive the GitHub body sanitizer — read back after posting, the body begins with the fenced block and the marker is gone, so a scan keyed on it would not see the report. There is no tool on this seat that can edit an existing issue comment, hence a second comment leading with the marker as literal text. This is the readable copy; the JSON is identical in both.
{
"issue": 5733,
"status": "done",
"branch": "claude/issue-5733-changeset-presence-population",
"pr": "https://github.com/objectstack-ai/objectui/pull/6073",
"premise_still_valid": true,
"summary": "The card's line 416 is stale; the population test was at scripts/check-changeset-presence.mjs:467 on a1c41c516, inside classifyChangedPaths, as path.startsWith(directory + '/src/'). Premise re-verified on current origin/main: appending one comment to apps/console/index.html and running the unmodified gate gave '1 file(s) changed, 0 of them under the src/ of a package the release covers' and exit 0. Widened the population to a package's PUBLISHED, EXECUTABLE source via a new exported isPublishedSource: (a) under PKG/src/ (unchanged, still all-of-it, answered first so nothing guarded before is guarded less), (b) PKG/index.html the bundler's HTML entry, (c) anything the package's own package.json files list publishes verbatim — minus documentation and licences (*.md, LICENSE*), which ship but are not behaviour. The rule is stated in the file header in checkable prose, alongside the boundaries deliberately left out (PKG/public/, PKG/*-preview.html, PKG/demo/, package.json) with the reasoning for each. Measured over every tracked file: 3330 guarded, and the widening adds exactly three — apps/console/index.html, apps/console/plugin.ts, packages/runner/index.html. No convention was invented: files is npm's own published-surface declaration and index.html is Vite's documented default build entry, and both packages with a root index.html do build it. The one arguable line is PKG/public/ (published verbatim by Vite, but static, not code) — excluded and argued in the header rather than left silent, flagged in open_questions for the maintainer.",
"tests": "All at final commit 50365b547, run from the repo ROOT. (1) scripts/__tests__/check-changeset-presence.test.ts: 'Test Files 1 passed (1)' / 'Tests 38 passed (38)' (7 new). (2) Whole scripts/__tests__/ suite: 'Test Files 64 passed (64)' / 'Tests 1770 passed (1770)'. (3) NON-VACUITY, all legs executable — positive: fixture change to apps/console/index.html with no changeset goes red, exit 1, output names apps/console/index.html under @fixture/console; negative control 1: same change plus a changeset goes green with 'declares 1 changeset'; negative control 2: four non-shipped neighbours in the SAME package directory (public/manifest.json, preview-gallery.html, README.md, vitest.config.ts) stay green with '4 file(s) changed, 0 of them published source'. Plus a clause table over isPublishedSource, a clause-(c) leg on plugin.ts, a leg proving a files-published README.md still does not count, and a real-tree leg pinning three-files-and-no-fourth. (4) ABLATION proving the new tests can fail: isPublishedSource reverted to `return relative.startsWith('src/')`. NO BUILD IS INVOLVED — the test imports ../check-changeset-presence.mjs directly by relative path, so there is no dist/ leg to rebuild; the mutation was confirmed on disk before reading any result (grep -c ABLATION-5733 = 1 injected, grep -c 'relative === HTML_ENTRY' = 0 removed), not from the editor's exit code. Result: 'Test Files 1 failed (1)' / 'Tests 5 failed | 33 passed (38)', the five being POSITIVE, NEGATIVE CONTROL 1, the clause-(c) leg, the clause table, and the real-tree leg. The two new tests that stayed green are both stays-green assertions, which is the correct direction for them, and their branches are covered by the clause table that did go red. Restored by an EXIT INT TERM trap and verified byte-identical with diff -q. (5) REVERSE-VERIFICATION on apps/console/index.html, direction predicted before running (red, naming the file): observed '2 file(s) changed, 1 of them published source of a package the release covers' then '1 source file(s) of 1 released package(s) changed, and this change adds no changeset:' / '@object-ui/console' / 'apps/console/index.html', exit 1. Mutation proven on disk (grep hit = 1, 9460 to 9505 bytes); restored with 'git checkout HEAD -- apps/console/index.html' (NOT the bare form); restoration proven by empty 'git diff HEAD' for the path, zero probe hits, bytes back to 9460, and 'git status --porcelain' showing only the intended edit. Same legs run for apps/console/plugin.ts (red naming it) and for five real non-shipped files at once (green). (6) GATES, each quoting its own verdict line: check-changeset-presence.mjs on this PR's own diff 'No source of a released package changed in this range, so no changeset is owed.' (exit 0 — no infinite regress); check-changeset-fixed.mjs 'All workspace packages are in the changeset fixed group.'; check-changeset-no-major.mjs 'No changeset declares a major bump.'; pnpm check:control-bytes 'check-control-bytes: OK (scanned 5016 tracked text file(s); skipped 85 binary).'; check-lint-coverage.mjs 'lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).'; check-type-check-coverage.mjs 'type-check coverage: 45/46 via type-check, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.'; pnpm type-check:scripts exit 0 with no diagnostics; eslint --no-inline-config --format json on both changed files reports 2 files, 0 errors, 0 warnings. Exit codes were captured by redirect-then-capture, never after a pipe. On eslint scope: this is NOT a narrowing — root 'pnpm lint' is 'turbo run lint', a per-package fan-out, and scripts/ is not a package, so pnpm lint reaches neither changed file; the targeted run uses the root eslint.config.js and its own --format json count of 2 is the complete lint population for this diff. node-esm-load-gate.yml is a nightly/dispatch workflow requiring a real build, not a per-PR gate for this diff; it is red in this unbuilt worktree naming '@object-ui/console: no-build-output — ./plugin.js does not exist after the build', a file this diff does not touch. CI convergence is the PM's to read.",
"open_questions": [
{
"question": "Should PKG/public/ join the guarded population? It is published verbatim (Vite copies it into dist/, so apps/console/public/manifest.json reaches every user) but is not executable, so it fell on the documentation side of the published-AND-executable line.",
"options": [
"A - leave it out, as implemented: static assets sit with the READMEs; a favicon or logo swap demanding a declaration is exactly the friction that trains authors to answer this gate with an empty changeset they did not read",
"B - guard it: manifest.json changes the installed PWA's name and display mode, which is user-visible even though no code runs"
],
"recommendation": "A, because the population's failure mode in the wide direction is the gate stopping being read at all, and manifest.json is one file against four static assets in the only public/ a released package has. The exclusion is argued in the gate header as a considered boundary rather than a silence, so moving to B later is a one-clause edit with the reasoning already on the page. Not blocking: this is flagged for the maintainer, not a needs_decision."
}
],
"out_of_scope_findings": []
}
Activity
[-]finding: check-changeset-presence's population is `<pkg>/src/**` only, so `apps/console/index.html` — shipped and executable — can change with no changeset required[/-][+]finding: check-changeset-presence counts only a package's own `src/` subtree, so `apps/console/index.html` — shipped and executable — can change with no changeset required[/+]os-zhuang commentedon Aug 23, 2026
Triage (first touch):
pm:queue+domain:devx. Thefindinglabel is removed in the same stroke — grading a finding means leaving the finding state, and a card carrying both would be counted as still-untriaged by the bare-label reading.Landing:
scripts/check-changeset-presence.mjs:416— thepath.startsWith(\${directory}/src/`)` population test. A gate's own source, so the engineering stream.Graded
pm:queuerather than left as an observation: the body carries the line number, the gate's own summary output as evidence, and a concrete shipped example (apps/console/index.htmlon PR #5732) where the hole was exercised. Nothing is open to ask.objectui three-stream split (maintainer 2026-08-21). Routed via the maintainer direct-dispatch channel, this session, verbatim: 「objectui 有 79 张卡在车道视图之外(占该仓 35%),你帮我分批处理」 · 「继续 批 2 批 3」. PM session
session_0124Qg8rLvpXnQDwCmpKUmaJ. Not a Routine triage fire — the triage seat may re-grade.Generated by Claude Code
yinlianghui-tw commentedon Aug 24, 2026
PM claim + dispatch order — R11
Claimed by the
domain:devx@ objectui execution seat (#5748), round R11, PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe. Branch:claude/issue-5733-changeset-presence-population.Triage graded this
pm:queuewith no open question: the body carries the line number (scripts/check-changeset-presence.mjs:416, thepath.startsWith(\${directory}/src/`)population test), the gate's own output as evidence, and a shipped example (apps/console/index.html` on PR #5732) where the hole was exercised.Re-derive before you change anything
That line number is from 2026-08-23 and this file has been edited since — several cards this round touched the changeset gates. Locate the population test by its code, not by its line number, and report where it actually is. Stale line numbers have already cost this lane a round today.
Also re-verify the premise itself: that a shipped, executable file outside a package's
src/can change with no changeset required. Show it as a reading on currentorigin/main, with the gate's own summary line.The judgment this card actually turns on
The fix is not "scan more files" — it is deciding what the gate's population should be, and that choice has a wrong answer in each direction:
Pick the population by what is published and executable, and state the rule you implemented in the file, in prose a future reader can check against.⚠️ If the honest answer is that the boundary needs a convention the repo has not settled, say so and report rather than inventing one — that would be a maintainer call.
Verification this card owes
apps/console/index.html. Predict the direction before running, prove the mutation reached disk by grepping the specific text, show the gate flip, restore withgit checkout HEAD -- <path>(⛔ not the bare form — it reads the mutation back and exits 0, objectstack#11648), and prove restoration with an emptygit diff HEAD.scripts/__tests__/suite from the repo root, not package-scoped.⛔ Out of scope:
check-changeset-fixed,check-changeset-no-major, thelanejob's mirror ofreadChangesetState(#5775 owns that — do not touch it, another seat may claim it), and the release workflow. ⛔ Do not weaken any gate to make a count come out even.⛔ Never
git stash— shared stack, other agents are using it now.Generated by Claude Code
yinlianghui-tw commentedon Aug 24, 2026
{ "issue": 5733, "status": "done", "branch": "claude/issue-5733-changeset-presence-population", "pr": "https://github.com/objectstack-ai/objectui/pull/6073", "premise_still_valid": true, "summary": "The card's line 416 is stale; the population test was at scripts/check-changeset-presence.mjs:467 on a1c41c516, inside classifyChangedPaths, as `path.startsWith(`${directory}/src/`)`. Premise re-verified on current origin/main: appending one comment to apps/console/index.html and running the unmodified gate gave `1 file(s) changed, 0 of them under the src/ of a package the release covers` and exit 0. Widened the population to a package's PUBLISHED, EXECUTABLE source via a new exported `isPublishedSource`: (a) under PKG/src/ (unchanged, still all-of-it, answered first so nothing guarded before is guarded less), (b) PKG/index.html the bundler's HTML entry, (c) anything the package's own package.json `files` list publishes verbatim — minus documentation and licences (*.md, LICENSE*), which ship but are not behaviour. The rule is stated in the file header in checkable prose, alongside the boundaries deliberately left out (PKG/public/, PKG/*-preview.html, PKG/demo/, package.json) with the reasoning for each. Measured over every tracked file: 3330 guarded, and the widening adds exactly three — apps/console/index.html, apps/console/plugin.ts, packages/runner/index.html. No convention was invented: `files` is npm's own published-surface declaration and index.html is Vite's documented default build entry, and both packages with a root index.html do build it. The one arguable line is PKG/public/ (published verbatim by Vite, but static, not code) — excluded and argued in the header rather than left silent, flagged in open_questions for the maintainer.", "tests": "All at final commit 50365b547, run from the repo ROOT. (1) scripts/__tests__/check-changeset-presence.test.ts: `Test Files 1 passed (1)` / `Tests 38 passed (38)` (7 new). (2) Whole scripts/__tests__/ suite: `Test Files 64 passed (64)` / `Tests 1770 passed (1770)`. (3) NON-VACUITY, all legs executable — positive: fixture change to apps/console/index.html with no changeset goes red, exit 1, output names `apps/console/index.html` under `@fixture/console`; negative control 1: same change plus a changeset goes green with `declares 1 changeset`; negative control 2: four non-shipped neighbours in the SAME package directory (public/manifest.json, preview-gallery.html, README.md, vitest.config.ts) stay green with `4 file(s) changed, 0 of them published source`. Plus a clause table over isPublishedSource, a clause-(c) leg on plugin.ts, a leg proving a `files`-published README.md still does not count, and a real-tree leg pinning three-files-and-no-fourth. (4) ABLATION proving the new tests can fail: isPublishedSource reverted to `return relative.startsWith('src/')`. NO BUILD IS INVOLVED — the test imports ../check-changeset-presence.mjs directly by relative path, so there is no dist/ leg to rebuild; the mutation was confirmed on disk before reading any result (`grep -c ABLATION-5733` = 1 injected, `grep -c 'relative === HTML_ENTRY'` = 0 removed), not from the editor's exit code. Result: `Test Files 1 failed (1)` / `Tests 5 failed | 33 passed (38)`, the five being POSITIVE, NEGATIVE CONTROL 1, the clause-(c) leg, the clause table, and the real-tree leg. The two new tests that stayed green are both stays-green assertions, which is the correct direction for them, and their branches are covered by the clause table that did go red. Restored by an `EXIT INT TERM` trap and verified byte-identical with `diff -q`. (5) REVERSE-VERIFICATION on apps/console/index.html, direction predicted before running (red, naming the file): observed `2 file(s) changed, 1 of them published source of a package the release covers` then `1 source file(s) of 1 released package(s) changed, and this change adds no changeset:` / `@object-ui/console` / `apps/console/index.html`, exit 1. Mutation proven on disk (grep hit = 1, 9460 -> 9505 bytes); restored with `git checkout HEAD -- apps/console/index.html` (NOT the bare form); restoration proven by empty `git diff HEAD` for the path, zero probe hits, bytes back to 9460, and `git status --porcelain` showing only the intended edit. Same legs run for apps/console/plugin.ts (red naming it) and for five real non-shipped files at once (green). (6) GATES, each quoting its own verdict line: check-changeset-presence.mjs on this PR's own diff `No source of a released package changed in this range, so no changeset is owed.` (exit 0 — no infinite regress); check-changeset-fixed.mjs `All workspace packages are in the changeset fixed group.`; check-changeset-no-major.mjs `No changeset declares a major bump.`; pnpm check:control-bytes `check-control-bytes: OK (scanned 5016 tracked text file(s); skipped 85 binary).`; check-lint-coverage.mjs `lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).`; check-type-check-coverage.mjs `type-check coverage: 45/46 via type-check, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.`; pnpm type-check:scripts exit 0 with no diagnostics; eslint --no-inline-config --format json on both changed files reports 2 files, 0 errors, 0 warnings. Exit codes were captured by redirect-then-capture, never after a pipe. On eslint scope: this is NOT a narrowing — root `pnpm lint` is `turbo run lint`, a per-package fan-out, and scripts/ is not a package, so pnpm lint reaches neither changed file; the targeted run uses the root eslint.config.js and its own --format json count of 2 is the complete lint population for this diff. node-esm-load-gate.yml is a nightly/dispatch workflow requiring a real build, not a per-PR gate for this diff; it is red in this unbuilt worktree naming `@object-ui/console: no-build-output — ./plugin.js does not exist after the build`, a file this diff does not touch. CI convergence is the PM's to read.", "open_questions": [ { "question": "Should PKG/public/ join the guarded population? It is published verbatim (Vite copies it into dist/, so apps/console/public/manifest.json reaches every user) but is not executable, so it fell on the documentation side of the published-AND-executable line.", "options": [ "A - leave it out, as implemented: static assets sit with the READMEs; a favicon or logo swap demanding a declaration is exactly the friction that trains authors to answer this gate with an empty changeset they did not read", "B - guard it: manifest.json changes the installed PWA's name and display mode, which is user-visible even though no code runs" ], "recommendation": "A, because the population's failure mode in the wide direction is the gate stopping being read at all, and manifest.json is one file against four static assets in the only public/ a released package has. The exclusion is argued in the gate header as a considered boundary rather than a silence, so moving to B later is a one-clause edit with the reasoning already on the page. Not blocking: this is flagged for the maintainer, not a needs_decision." } ], "out_of_scope_findings": [] }Generated by Claude Code
yinlianghui-tw commentedon Aug 24, 2026
os-dev-report
Re-post of the report in #issuecomment-5397616781, unchanged. That comment's leading HTML-comment marker did not survive the GitHub body sanitizer — read back after posting, the body begins with the fenced block and the marker is gone, so a scan keyed on it would not see the report. There is no tool on this seat that can edit an existing issue comment, hence a second comment leading with the marker as literal text. This is the readable copy; the JSON is identical in both.
{ "issue": 5733, "status": "done", "branch": "claude/issue-5733-changeset-presence-population", "pr": "https://github.com/objectstack-ai/objectui/pull/6073", "premise_still_valid": true, "summary": "The card's line 416 is stale; the population test was at scripts/check-changeset-presence.mjs:467 on a1c41c516, inside classifyChangedPaths, as path.startsWith(directory + '/src/'). Premise re-verified on current origin/main: appending one comment to apps/console/index.html and running the unmodified gate gave '1 file(s) changed, 0 of them under the src/ of a package the release covers' and exit 0. Widened the population to a package's PUBLISHED, EXECUTABLE source via a new exported isPublishedSource: (a) under PKG/src/ (unchanged, still all-of-it, answered first so nothing guarded before is guarded less), (b) PKG/index.html the bundler's HTML entry, (c) anything the package's own package.json files list publishes verbatim — minus documentation and licences (*.md, LICENSE*), which ship but are not behaviour. The rule is stated in the file header in checkable prose, alongside the boundaries deliberately left out (PKG/public/, PKG/*-preview.html, PKG/demo/, package.json) with the reasoning for each. Measured over every tracked file: 3330 guarded, and the widening adds exactly three — apps/console/index.html, apps/console/plugin.ts, packages/runner/index.html. No convention was invented: files is npm's own published-surface declaration and index.html is Vite's documented default build entry, and both packages with a root index.html do build it. The one arguable line is PKG/public/ (published verbatim by Vite, but static, not code) — excluded and argued in the header rather than left silent, flagged in open_questions for the maintainer.", "tests": "All at final commit 50365b547, run from the repo ROOT. (1) scripts/__tests__/check-changeset-presence.test.ts: 'Test Files 1 passed (1)' / 'Tests 38 passed (38)' (7 new). (2) Whole scripts/__tests__/ suite: 'Test Files 64 passed (64)' / 'Tests 1770 passed (1770)'. (3) NON-VACUITY, all legs executable — positive: fixture change to apps/console/index.html with no changeset goes red, exit 1, output names apps/console/index.html under @fixture/console; negative control 1: same change plus a changeset goes green with 'declares 1 changeset'; negative control 2: four non-shipped neighbours in the SAME package directory (public/manifest.json, preview-gallery.html, README.md, vitest.config.ts) stay green with '4 file(s) changed, 0 of them published source'. Plus a clause table over isPublishedSource, a clause-(c) leg on plugin.ts, a leg proving a files-published README.md still does not count, and a real-tree leg pinning three-files-and-no-fourth. (4) ABLATION proving the new tests can fail: isPublishedSource reverted to `return relative.startsWith('src/')`. NO BUILD IS INVOLVED — the test imports ../check-changeset-presence.mjs directly by relative path, so there is no dist/ leg to rebuild; the mutation was confirmed on disk before reading any result (grep -c ABLATION-5733 = 1 injected, grep -c 'relative === HTML_ENTRY' = 0 removed), not from the editor's exit code. Result: 'Test Files 1 failed (1)' / 'Tests 5 failed | 33 passed (38)', the five being POSITIVE, NEGATIVE CONTROL 1, the clause-(c) leg, the clause table, and the real-tree leg. The two new tests that stayed green are both stays-green assertions, which is the correct direction for them, and their branches are covered by the clause table that did go red. Restored by an EXIT INT TERM trap and verified byte-identical with diff -q. (5) REVERSE-VERIFICATION on apps/console/index.html, direction predicted before running (red, naming the file): observed '2 file(s) changed, 1 of them published source of a package the release covers' then '1 source file(s) of 1 released package(s) changed, and this change adds no changeset:' / '@object-ui/console' / 'apps/console/index.html', exit 1. Mutation proven on disk (grep hit = 1, 9460 to 9505 bytes); restored with 'git checkout HEAD -- apps/console/index.html' (NOT the bare form); restoration proven by empty 'git diff HEAD' for the path, zero probe hits, bytes back to 9460, and 'git status --porcelain' showing only the intended edit. Same legs run for apps/console/plugin.ts (red naming it) and for five real non-shipped files at once (green). (6) GATES, each quoting its own verdict line: check-changeset-presence.mjs on this PR's own diff 'No source of a released package changed in this range, so no changeset is owed.' (exit 0 — no infinite regress); check-changeset-fixed.mjs 'All workspace packages are in the changeset fixed group.'; check-changeset-no-major.mjs 'No changeset declares a major bump.'; pnpm check:control-bytes 'check-control-bytes: OK (scanned 5016 tracked text file(s); skipped 85 binary).'; check-lint-coverage.mjs 'lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).'; check-type-check-coverage.mjs 'type-check coverage: 45/46 via type-check, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.'; pnpm type-check:scripts exit 0 with no diagnostics; eslint --no-inline-config --format json on both changed files reports 2 files, 0 errors, 0 warnings. Exit codes were captured by redirect-then-capture, never after a pipe. On eslint scope: this is NOT a narrowing — root 'pnpm lint' is 'turbo run lint', a per-package fan-out, and scripts/ is not a package, so pnpm lint reaches neither changed file; the targeted run uses the root eslint.config.js and its own --format json count of 2 is the complete lint population for this diff. node-esm-load-gate.yml is a nightly/dispatch workflow requiring a real build, not a per-PR gate for this diff; it is red in this unbuilt worktree naming '@object-ui/console: no-build-output — ./plugin.js does not exist after the build', a file this diff does not touch. CI convergence is the PM's to read.", "open_questions": [ { "question": "Should PKG/public/ join the guarded population? It is published verbatim (Vite copies it into dist/, so apps/console/public/manifest.json reaches every user) but is not executable, so it fell on the documentation side of the published-AND-executable line.", "options": [ "A - leave it out, as implemented: static assets sit with the READMEs; a favicon or logo swap demanding a declaration is exactly the friction that trains authors to answer this gate with an empty changeset they did not read", "B - guard it: manifest.json changes the installed PWA's name and display mode, which is user-visible even though no code runs" ], "recommendation": "A, because the population's failure mode in the wide direction is the gate stopping being read at all, and manifest.json is one file against four static assets in the only public/ a released package has. The exclusion is argued in the gate header as a considered boundary rather than a silence, so moving to B later is a one-clause edit with the reasoning already on the page. Not blocking: this is flagged for the maintainer, not a needs_decision." } ], "out_of_scope_findings": [] }Generated by Claude Code