Skip to content

Propagate the docstring gate entry guard fix so an unresolvable entry cannot pass the gate - #59

Merged
unbraind merged 5 commits into
mainfrom
fix-docstring-gate-entry-guard
Aug 10, 2026
Merged

Propagate the docstring gate entry guard fix so an unresolvable entry cannot pass the gate#59
unbraind merged 5 commits into
mainfrom
fix-docstring-gate-entry-guard

Conversation

@unbraind

@unbraind unbraind commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Propagate the docstring gate entry guard fix

Summary

The isMainInvocation guard in scripts/docstring-gate.ts caught realpathSync
errors and returned false. When argv[1] could not be resolved, the top-level
selector called the no-op placeholder instead of main, so npm run docstring
exited 0 having scanned nothing — a mandatory release gate reporting success
without doing its job.

The corrected implementation propagates the realpathSync error. A broken
environment must not silently satisfy a gate; crashing loudly is the safe outcome.

Changes

  • scripts/docstring-gate.ts: isMainInvocation now propagates realpathSync
    errors instead of catching them and returning false. The JSDoc is updated to
    document the new @throws contract and the rationale.
  • test/docstring-gate.test.ts: the test asserting false for an unresolvable
    argv[1] is replaced with one asserting assert.throws(..., /ENOENT/).

Verification

  • npm test — 216 tests pass
  • npm run docstring — exits 0, prints the "N file(s), N declaration(s)" line
  • Manual check: isMainInvocation(["node","/nonexistent/x.ts"],"file:///x")
    throws ENOENT (printed "GOOD: threw ENOENT")

pm item

pm-graph-r8nz

Summary by Sourcery

Ensure the docstring gate fails loudly when its entry script path cannot be resolved instead of silently skipping the gate.

Bug Fixes:

  • Propagate realpathSync errors from the docstring gate entry guard so an unresolvable argv[1] causes a non-zero failure rather than a no-op success.

Enhancements:

  • Clarify docstring gate entry guard documentation to describe error propagation semantics and rationale.

Documentation:

  • Document the fix in the changelog and project issue history for the docstring gate entry guard behavior.

Tests:

  • Update docstring gate tests to assert ENOENT is thrown for an unresolvable entry path rather than returning false.

Summary by cubic

Prevent silent skips of the docstring gate. isMainInvocation now canonicalizes both the entry path and moduleUrl and propagates realpathSync errors; also drops an unused import so symlinked or unresolvable entries fail loudly.

  • Bug Fixes
    • Canonicalize both argv[1] and moduleUrl with realpathSync; propagate errors. Tests use process.execPath, assert ENOENT, and pin the symlinked moduleUrl case.
    • Remove unused pathToFileURL import.

Written for commit 949af97. Summary will update on new commits.

Review in cubic

…ing the gate

The isMainInvocation guard caught realpathSync errors and returned false. When
argv[1] could not be resolved, the top-level selector called the no-op
placeholder instead of main, so npm run docstring exited 0 having scanned
nothing — a mandatory release gate reporting success without doing its job.

The corrected implementation propagates the realpathSync error. The case
requires argv[1] to stop resolving after Node has already loaded this file, so
in practice it means the environment is broken, and a broken environment must
not silently satisfy a gate. Crashing loudly is the safe outcome.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR changes the docstring gate’s main-invocation guard so that an unresolvable entry path now propagates the underlying filesystem error instead of silently declining to run, updates tests to assert the new failure mode, and records the fix in the changelog and project management artifacts.

Sequence diagram for docstring gate main-invocation error propagation

sequenceDiagram
  actor Developer
  participant npm
  participant NodeProcess
  participant DocstringGateScript
  participant Filesystem

  Developer->>npm: npm run docstring
  npm->>NodeProcess: start docstring-gate entry
  NodeProcess->>DocstringGateScript: invoke isMainInvocation(argv, moduleUrl)
  DocstringGateScript->>Filesystem: realpathSync(argv[1])
  Filesystem-->>DocstringGateScript: ENOENT error
  Note right of DocstringGateScript: realpathSync error is propagated
  DocstringGateScript-->>NodeProcess: throw ENOENT
  NodeProcess-->>npm: non-zero exit
  npm-->>Developer: docstring gate fails loudly (scan not run)
Loading

File-Level Changes

Change Details Files
Change isMainInvocation to propagate realpathSync errors instead of treating them as a non-main invocation.
  • Remove try/catch around realpathSync and let its errors propagate
  • Clarify JSDoc to describe the propagated error behavior and rationale for failing loudly
  • Document that false is returned only when argv[1] resolves to a different module URL
scripts/docstring-gate.ts
Update the docstring gate tests to expect an ENOENT throw for an unresolvable entry path.
  • Replace prior assertion that isMainInvocation returns false on an unresolvable argv[1] with assert.throws matching ENOENT
  • Adjust test description and comments to reflect that crashing is the safe outcome
test/docstring-gate.test.ts
Record the fix in project documentation and PM history.
  • Add a Fixed entry describing the propagated docstring gate behavior to the Unreleased changelog section
  • Add new PM issue and history entries for pm-graph-r8nz
CHANGELOG.md
.agents/pm/history/pm-graph-r8nz.jsonl
.agents/pm/issues/pm-graph-r8nz.toon

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dd4d2c5d-7f85-4ca6-8523-79751eb2787b

📥 Commits

Reviewing files that changed from the base of the PR and between 648b91e and 949af97.

📒 Files selected for processing (5)
  • .agents/pm/history/pm-graph-r8nz.jsonl
  • .agents/pm/issues/pm-graph-r8nz.toon
  • CHANGELOG.md
  • scripts/docstring-gate.ts
  • test/docstring-gate.test.ts

Summary by CodeRabbit

  • Bug Fixes

    • Fixed docstring validation so unresolved entry paths now report an error instead of silently completing.
    • Improved handling of symlinked module paths, including preserved-symlink scenarios.
    • Preserved expected behavior for valid entry paths and non-matching modules.
  • Documentation

    • Added an Unreleased changelog entry describing these fixes.
  • Tests

    • Added coverage for missing entry paths and symlinked module resolution.

Walkthrough

The docstring gate now canonicalizes the entry path and module URL. It propagates realpathSync errors for unresolved entry paths. Tests cover symlinked modules and ENOENT. Changelog and PM records document the fix.

Changes

Docstring gate fix

Layer / File(s) Summary
Canonical path guard and verification
scripts/docstring-gate.ts, test/docstring-gate.test.ts
isMainInvocation uses canonical paths for both inputs and propagates unresolved path errors. Tests cover direct invocation, symlinked modules, and ENOENT.
Fix records and release note
CHANGELOG.md, .agents/pm/issues/*, .agents/pm/history/*
The changelog and PM records describe the fix, verification results, affected files, and resolution status.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • unbraind/pm-graph#58: Introduced the isMainInvocation implementation and its tests refined by this change.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the docstring gate fix, error propagation, symlink handling, tests, and verification results.
Title check ✅ Passed The title clearly identifies the primary change: preventing unresolved entry paths from silently passing the docstring gate.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-docstring-gate-entry-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR makes the docstring gate fail loudly when its entry path cannot be resolved and canonicalizes both entry-point paths for symlink-preserving Node configurations.

  • Removes the exception-swallowing entry guard behavior.
  • Canonicalizes both argv[1] and moduleUrl before comparison.
  • Adds regression coverage for unresolved entries and symlinked module URLs.
  • Updates the changelog and project issue history.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
scripts/docstring-gate.ts Propagates path-resolution failures and compares canonical paths on both sides, completing the previously requested entry-guard fix.
test/docstring-gate.test.ts Covers ENOENT propagation and the symlinked moduleUrl behavior produced by Node symlink-preservation flags.
CHANGELOG.md Records the docstring-gate fix in the unreleased changelog.

Reviews (9): Last reviewed commit: "refactor(gate): drop the import left unu..." | Re-trigger Greptile

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai full review
@greptileai

Reviewer context — this is a small change with an inverted premise behind it, so please read
the reasoning rather than just the diff:

isMainInvocation used to catch a realpathSync failure and return false. The top-level
selector reads false as "not the entry point" and calls the no-op placeholder, so
npm run docstring exited 0 having scanned nothing — a required release check reporting
success without doing its job, which is the single failure this gate exists to prevent.
Letting realpathSync throw turns that into a loud non-zero exit.

The original comment and its test both used the phrase "fail closed" to mean "does not
crash". That inverts it: here the crash is the safe outcome. Because this launcher was
copied between repositories rather than published as a dependency, the wrong reasoning was
copied with it and appeared independently confirmed in every adopting repo — code, comment
and test all agreeing with each other and all wrong.

Specific things worth checking:

  1. The replacement test asserts the throw (assert.throws(..., /ENOENT/)) rather than
    accepting either outcome.
  2. A genuinely different entry path must still return false — that is how a test importing
    the module declines to run the gate. Only an unresolvable entry should propagate.
  3. Coverage thresholds are untouched; please confirm nothing was weakened to accommodate the
    removed branch.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@unbraind have exceeded the limit for the number of chat messages per hour. Please wait 44 minutes and 55 seconds before sending another message.

…gate

Greptile and CodeRabbit independently flagged the same hole in the fix from
the previous commit, on two different repositories.

Comparing `pathToFileURL(realpathSync(entry)).href` against a raw
`moduleUrl` resolves only one side. That is sufficient under Node's
defaults, where the ESM loader realpaths a module before recording
`import.meta.url`. Under `--preserve-symlinks` or
`--preserve-symlinks-main` it is not: `moduleUrl` keeps the symlink while
`realpathSync(entry)` resolves it, so a direct invocation through a symlink
compares unequal, the selector calls the placeholder, and `npm run
docstring` exits 0 without scanning. That is the exact silent skip this
function exists to prevent, reintroduced by a launch flag.

Measured rather than argued. With `moduleUrl` holding the symlink path:

  both-sides (new): true
  one-sided (old):  false

Canonicalising both sides costs one syscall and removes the dependence on
how Node was launched.

The tests also now use `process.execPath` rather than the literal "node",
so the argv matches a real invocation on systems where the binary is named
differently, and assert on `error.code === "ENOENT"` rather than matching
the message text, which is not part of Node's contract.
@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai full review
@greptileai

New commits since the last pass. All previous threads on this PR are replied to, voted on and
resolved.

What changed and why, because the second round came from you rather than from me:

Greptile (on pm-beads) and CodeRabbit (on pm-slack) independently found that the fix in the
first round was itself incomplete. It compared pathToFileURL(realpathSync(entry)).href
against a raw moduleUrl — resolving one side only. That is sufficient under Node's
defaults, where the ESM loader realpaths a module before recording import.meta.url. It is
not sufficient under --preserve-symlinks / --preserve-symlinks-main, where moduleUrl
keeps the symlink while realpathSync(entry) resolves it. The comparison then returns false
on a direct invocation, the selector calls the no-op placeholder, and npm run docstring
exits 0 without scanning — the exact silent skip this PR exists to remove, reachable via a
launch flag.

Measured with moduleUrl holding the symlink path:

both-sides (new): true
one-sided (old):  false

Both sides are now canonicalised. Also applied from Sourcery on sibling PRs: process.execPath
in place of the literal "node", and an error-code predicate (error.code === "ENOENT")
rather than a message regex, since the message text is not part of Node's contract and a regex
would match any error whose text happens to contain ENOENT.

What is most worth your attention this round:

  1. Does canonicalising both sides break the negative case? A genuinely different entry path
    must still return false — that is how a test import declines to run the gate. Only an
    unresolvable path should propagate.
  2. Coverage thresholds are untouched in every repo. If you see a threshold moved, that is a
    finding I want.
  3. Is there any remaining path by which this guard can return false for a real direct
    invocation? That is the failure mode with teeth; a spurious throw is loud and harmless by
    comparison.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== PR diff summary ==='
git diff --stat HEAD^ HEAD
echo
echo '=== Changed files ==='
git diff --name-only HEAD^ HEAD
echo
echo '=== Latest diff ==='
git diff --no-ext-diff --unified=80 HEAD^ HEAD -- \
  scripts/docstring-gate.ts \
  test/docstring-gate.test.ts \
  package.json \
  CHANGELOG.md \
  .agents/pm/issues/pm-graph-r8nz.toon \
  .agents/pm/history/pm-graph-r8nz.jsonl

echo
echo '=== scripts/docstring-gate.ts outline ==='
ast-grep outline scripts/docstring-gate.ts --items all --view expanded || true
echo
echo '=== test/docstring-gate.test.ts outline ==='
ast-grep outline test/docstring-gate.test.ts --items all --view expanded || true

echo
echo '=== isMainInvocation definitions and call sites ==='
rg -n -C 5 --glob '!node_modules/**' \
  '\bisMainInvocation\s*(?:\(|[:=])|isMainInvocation' \
  scripts test package.json . 2>/dev/null || true

echo
echo '=== Coverage settings and thresholds ==='
rg -n -i -C 5 --glob '!node_modules/**' \
  '(coverageThreshold|thresholds?|coverage.*(?:line|branch|function|statement)|(?:line|branch|function|statement).*(?:coverage|threshold))' \
  . 2>/dev/null || true

echo
echo '=== Test and docstring scripts ==='
rg -n -C 3 '"(?:test|docstring|coverage)"\s*:' package.json 2>/dev/null || true

Length of output: 53881


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Refs and candidate PR base ==='
git branch --show-current
git show-ref --verify --quiet refs/remotes/origin/main && echo 'origin/main is available' || true
base="$(git merge-base HEAD origin/main)"
printf 'merge base: %s\n' "$base"

echo
echo '=== Complete PR changed files ==='
git diff --name-status "$base" HEAD

echo
echo '=== Complete PR diff: executable and configuration files ==='
git diff --no-ext-diff --unified=50 "$base" HEAD -- \
  scripts/docstring-gate.ts \
  test/docstring-gate.test.ts \
  package.json \
  scripts/coverage-gate.ts

echo
echo '=== All symlink-preservation and URL/path guard tests ==='
rg -n -i -C 6 --glob '!node_modules/**' \
  '(preserve-symlinks|preserve-symlinks-main|symlinkSync|fileURLToPath|isMainInvocation)' \
  test scripts

echo
echo '=== Full current guard-test section ==='
sed -n '135,190p' test/docstring-gate.test.ts

echo
echo '=== Coverage configuration changed in complete PR ==='
git diff --no-ext-diff --unified=20 "$base" HEAD -- package.json scripts/coverage-gate.ts

Length of output: 32989


@unbraind Full review complete.

I found one test gap.

  • test/docstring-gate.test.ts, Line 160, passes pathToFileURL(gatePath).href as moduleUrl. This is the canonical path.
  • The old one-sided comparison also passes this test.
  • Add a case where moduleUrl is pathToFileURL(link).href. This models --preserve-symlinks and --preserve-symlinks-main. It must assert true for argv[1] === link.

The implementation itself is correct. It canonicalises both paths, so a real direct invocation returns true when both paths resolve to the same file. A different resolvable entry path still returns false. An unresolvable entry path propagates ENOENT.

The complete PR range does not modify package.json or scripts/coverage-gate.ts. The configured thresholds remain unchanged: 81 lines, 77 branches, and 88 functions.

⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 6 minutes.

…ails

The existing symlink test passes argv[1] as the link and moduleUrl as the
REAL path, which the old one-sided comparison pathToFileURL(realpathSync
(entry)).href === moduleUrl also satisfied — it could not distinguish the
fixed implementation from the broken one.

The new test puts the symlink in moduleUrl (pathToFileURL(link).href),
which is what Node records in import.meta.url under --preserve-symlinks /
--preserve-symlinks-main. The old comparison resolves argv[1] to the real
path and compares it to the symlink URL, which is false, so the gate
silently skips. The canonicalized comparison resolves both sides through
realpathSync and returns true.

Measured for this repo: reverting to the old one-sided comparison makes
the new test fail (fail 1), restoring the canonicalization makes it pass
(fail 0). The existing symlink test is retained — argv[1] arriving through
an npm bin shim is a separate real case.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/docstring-gate.ts`:
- Around line 116-127: Update the explanatory comment near the canonicalization
logic to remove the claim that resolving both paths costs one syscall; describe
the cost only as additional filesystem work or omit the cost statement. Keep the
behavior and rationale for canonicalizing both sides unchanged, including the
realpathSync calls in the direct-invocation gate.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 165f2b37-ebac-4291-be96-6a493d20cc4b

📥 Commits

Reviewing files that changed from the base of the PR and between eb82a24 and 4719e91.

📒 Files selected for processing (4)
  • .agents/pm/history/pm-graph-r8nz.jsonl
  • .agents/pm/issues/pm-graph-r8nz.toon
  • scripts/docstring-gate.ts
  • test/docstring-gate.test.ts

Comment thread scripts/docstring-gate.ts Outdated
@unbraind

Copy link
Copy Markdown
Owner Author

@greptileai

New commit since the last pass. (Deliberately not @-mentioning CodeRabbit this round —
mentioning it on many PRs at once exhausted its hourly chat quota earlier and produced green
checks whose body read "Review rate limited", which is a review that never happened. Its
automatic incremental review covers the new commit.)

What changed: CodeRabbit found that the symlink regression test added in the previous
round could not distinguish the fixed implementation from the broken one. It passed the link
as argv[1] and the real path as moduleUrl — and since realpathSync(link) resolves to
that same real path, the old one-sided comparison satisfied it too. A regression test that
passes against the bug it guards is not a regression test.

The added test puts the symlink in moduleUrl, which is what Node records in
import.meta.url under --preserve-symlinks. Verified by reverting the one-line
implementation change and re-running the gate test file:

one-sided (old):  fail 1
both-sides (new): fail 0

The original symlink test is kept — argv[1] through an npm bin shim is a real and separate
case.

Worth your attention: this is the second time in this series that something which looked
like a working check was not one — the gate exited 0 without scanning, and then the test
guarding that fix passed without discriminating. Same shape, one level up. If you see any
other assertion in this file that would still pass with the implementation reverted, that is
the finding I want most.

CodeRabbit flagged that the comment said canonicalising both sides costs one
syscall while the function calls realpathSync twice, and each resolution can
itself require several filesystem operations. The claim was mine and it was
copied into every adopting repository along with the fix.

The accurate statement is that it adds a second realpathSync. What the
comment is actually justifying is the removal of a dependence on how Node
was launched, and that argument does not need a cost figure to stand.
@unbraind

Copy link
Copy Markdown
Owner Author

@greptileai

One more commit: a comment-only correction, no behaviour change.

CodeRabbit found that the JSDoc claimed canonicalising both sides "costs one syscall" while
the function calls realpathSync twice, and each resolution can itself require several
filesystem operations. The number was invented and wrong in both directions. It now reads
"adds a second realpathSync", which is what is actually true and is all the argument needs.

Worth noting how far it travelled: the claim was mine and was copied verbatim into 14
repositories
along with the fix. One invented number became fourteen wrong comments —
the same failure mode this PR series exists to fix, where a copied launcher carried the
comment justifying its own bug into every adopting repo, so the wrong reasoning looked
independently confirmed everywhere.

Nothing else changed in this commit. If you see any other claim in this file that is stated
with more precision than it can support, that is the finding I want.

@unbraind

Copy link
Copy Markdown
Owner Author

@greptileai

Final review pass — no further changes are planned for this PR.

Since your last look the only delta is a comment-only correction: the JSDoc claimed
canonicalising both sides "costs one syscall" while the function calls realpathSync twice.
CodeRabbit caught it; the number was mine and had been copied into 14 repositories along with
the fix.

State of this PR:

  • every review thread replied to, voted on and resolved
  • all required checks green
  • gates verified locally: tests, docstring gate, coverage (thresholds unchanged), and
    changelog:check
  • the regression test is revert-proofed — reverting the implementation makes it fail

One correction to this PR's own description, raised by you on pm-github#32 and applicable
across the series: six repositories were already both-sides canonicalized on main
(pm-beads, pm-gantt-chart, pm-github, pm-jira, pm-slack, pm-slack-standup), so for those the
net change is only the catch removal and the commit subject overstated it. Nine genuinely
moved one-sided → both-sided. Details are in a comment on this PR where it applies.

If you have no further findings, this is ready to merge.

DeepScan flagged one new issue on these PRs and this is it: switching to
`realpathSync(entry) === realpathSync(fileURLToPath(moduleUrl))` removed the
last use of `pathToFileURL` in this file, but the import stayed.

Nothing else caught it. These packages have no lint script, and typecheck
does not enable noUnusedLocals, so the only gate that saw it was the
advisory one.
@unbraind

Copy link
Copy Markdown
Owner Author

@greptileai

One more commit, and it came from DeepScan — the advisory check that was reporting
1 new and 0 fixed issues on this PR.

Switching the entry guard to realpathSync(entry) === realpathSync(fileURLToPath(moduleUrl))
removed the last use of pathToFileURL in scripts/docstring-gate.ts, but the import stayed.
Removed now, across all 15 repositories carrying the change.

Nothing mandatory caught it. These packages have no lint script, typecheck does not
enable noUnusedLocals, and the docstring gate checks for the presence of documentation
rather than for dead code. Tests, coverage and changelog:check all passed with the unused
import in place. The only gate positioned to see it was the advisory, paywalled one — and its
detail is not readable through the API, so the count alone (1 new, identical on two PRs
carrying the identical change) is what made the hypothesis testable.

Tracked as a fleet gap rather than patched here: every package should either have a lint
script that flags unused imports, or noUnusedLocals in its typecheck config. Not attempted
in this PR because it would touch every repository while the review queue is already
saturated.

Verified after the change: typecheck, tests, docstring gate and changelog:check all pass.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown

The cleanup is clean. pathToFileURL is gone, replaced by fileURLToPath — the import now matches actual usage.

The fleet gap framing is exactly right. The advisory caught a cosmetic issue (unused import), not a behavioral one, and doing so only because the substantive change happened to leave a dead symbol behind. A "lint": "tsc --noUnusedLocals" or an eslint no-unused-vars rule in each package would have made this mandatory and visible in CI without needing the advisory layer at all.

One thing worth noting for the tracking issue: noUnusedLocals in tsconfig.json won't help if typecheck is run with tsc --noEmit against a config that doesn't set it — the flag has to actually be in the tsconfig or passed explicitly. Worth confirming which pattern the fleet uses before rolling it out, so the fix doesn't get applied to only half the packages.

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final pass — no further changes planned. The only delta since your last review is the
comment-only correction you asked for (the JSDoc claimed the extra resolution "costs one
syscall" while realpathSync is called twice). All threads are replied to, voted on and
resolved, all required checks are green, and the regression test is revert-proofed.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

@unbraind I will review the final changes, including the JSDoc correction and the regression coverage.

⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Requesting a review of the current head. No further changes are planned for this PR.

The commits since your last look are the ones you and Greptile asked for: removing the import left unused by the two-sided comparison (DeepScan caught it; nothing mandatory did, since these packages have no lint script and typecheck does not enable noUnusedLocals), and correcting comment wording that referenced a test not present in this file.

All threads are replied to, voted on and resolved; required checks are green; the regression test is revert-proofed (reverting the implementation makes it fail).

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@unbraind have exceeded the limit for the number of chat messages per hour. Please wait 2 minutes and 38 seconds before sending another message.

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Requesting a review of the current head. No further changes are planned.

The automatic review of this head returned a rate-limit notice rather than a review, and the incremental system then treats the commit as already reviewed, so it never retries on its own. The green check therefore reflects an attempt, not a review.

The diff is the same change already reviewed and merged on seven sibling PRs. All threads are replied to, voted on and resolved; required checks are green.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

if git show-ref --verify --quiet refs/remotes/origin/main; then
  base="$(git merge-base HEAD origin/main)"
else
  base="$(git rev-parse HEAD^)"
fi

printf '=== Base ===\n%s\n' "$base"
printf '\n=== Changed files ===\n'
git diff --name-status "$base" HEAD

printf '\n=== Guard and test diff ===\n'
git diff --no-ext-diff --unified=80 "$base" HEAD -- \
  scripts/docstring-gate.ts \
  test/docstring-gate.test.ts

printf '\n=== Current guard outline ===\n'
ast-grep outline scripts/docstring-gate.ts --items all --view expanded

printf '\n=== Current guard implementation ===\n'
rg -n -C 30 --glob '!node_modules/**' \
  'function isMainInvocation|const isMainInvocation|isMainInvocation\s*=' \
  scripts/docstring-gate.ts

printf '\n=== Current guard test cases ===\n'
rg -n -C 18 --glob '!node_modules/**' \
  'isMainInvocation|preserve-symlinks|symlinkSync|ENOENT|realpathSync' \
  test/docstring-gate.test.ts

printf '\n=== Coverage configuration changes ===\n'
git diff --no-ext-diff --unified=40 "$base" HEAD -- \
  package.json \
  scripts/coverage-gate.ts \
  .c8rc \
  .nycrc \
  vitest.config.ts \
  jest.config.js

printf '\n=== Current coverage threshold definitions ===\n'
rg -n -i -C 4 --glob '!node_modules/**' \
  '(coverageThreshold|thresholds?|coverage.*(?:line|branch|function|statement)|(?:line|branch|function|statement).*(?:coverage|threshold))' \
  package.json scripts . 2>/dev/null || true

Length of output: 46009


@unbraind Review complete. I found no issues in the current head.

isMainInvocation now canonicalizes both paths. It propagates unresolved-path errors. It still returns false for a different resolvable entry path.

The regression test places the symlink in moduleUrl. This test distinguishes the two-sided comparison from the prior one-sided comparison.

The PR does not modify coverage configuration. The thresholds remain 81 lines, 77 branches, and 88 functions.

I performed a static review. I did not run repository code in this sandbox.

You are interacting with an AI system.

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@unbraind

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@unbraind

Copy link
Copy Markdown
Owner Author

Merging. CodeRabbit's full review of the current head finished with no findings; all threads resolved; required checks green.

This repo genuinely moved one-sided → both-sided, so the canonicalization is substantive here rather than only the catch removal. Resolving just argv[1] breaks under --preserve-symlinks, where moduleUrl keeps the symlink: measured with moduleUrl holding the symlink path, the old form returns false and the new returns true — a launch flag could have reintroduced the exact silent skip this PR removes. Reverting the one-line change makes the regression test fail.

Note on the review itself: a plain @coderabbitai review kept answering Already reviewed here, because a rate-limited automatic attempt had marked this head as reviewed. full review is what bypasses that state — worth knowing, since the green check in between reflected an attempt rather than a review.

@unbraind
unbraind merged commit a6ec318 into main Aug 10, 2026
10 checks passed
@unbraind
unbraind deleted the fix-docstring-gate-entry-guard branch August 10, 2026 13:23
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@unbraind Acknowledged. The merge rationale is sound.

The two-sided canonicalization prevents the --preserve-symlinks false-negative path. The regression test distinguishes the fixed implementation from the one-sided implementation.

full review bypassed the stale incremental reviewed-state after the rate-limited attempt.

You are interacting with an AI system.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant