Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ jobs:
run: npm run check:html
- name: Format check
run: npm run format:check
- name: Release consistency
run: npm run check:release
# Builds the exact Chrome Web Store upload and fails on CSP violations,
# unresolved imports, dev files, icon/manifest problems, or permissions
# without a store justification.
- name: Package extension
run: npm run package
- uses: actions/upload-artifact@v4
with:
name: repolens-extension-zip
path: dist/
if-no-files-found: error
- name: Dependency audit
run: npm audit --audit-level=high

Expand Down
62 changes: 23 additions & 39 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,44 @@ permissions:
jobs:
package:
runs-on: ubuntu-latest
permissions:
contents: write # attach the zip to the GitHub Release on tag pushes
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Verify version matches tag
if: startsWith(github.ref, 'refs/tags/')
- name: Release consistency
run: |
VERSION="${GITHUB_REF_NAME#v}"
VERSION="$VERSION" node - <<'NODE'
const fs = require('fs');
const version = process.env.VERSION;
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8')).version;
if (pkg !== version || manifest !== version) {
console.error(`Version mismatch: tag=${version} package=${pkg} manifest=${manifest}`);
process.exit(1);
}
NODE
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
npm run check:release -- --tag "$GITHUB_REF_NAME"
else
npm run check:release
fi
- name: Verify
run: |
npm test
npm run lint
npm run check:html
npm run format:check
npm audit --audit-level=high
# Deterministic, validated build — the same zip goes to the Chrome Web Store.
- name: Package Chrome extension
run: |
VERSION=$(node -p "require('./manifest.json').version")
ZIP="dist/repolens-v${VERSION}.zip"
rm -rf dist repolens-release
mkdir -p dist repolens-release

cp manifest.json README.md CHANGELOG.md repolens-release/
cp batch.html library.html options.html output-tab.html share.html stack-tab.html whats-new.html repolens-release/
cp ./*.css repolens-release/
cp -R src icons assets vendor repolens-release/
(cd repolens-release && zip -r "../${ZIP}" . -x '*.DS_Store')

unzip -Z1 "$ZIP" | sort > /tmp/repolens-zip-files.txt
cat /tmp/repolens-zip-files.txt
forbidden='(^|/)(node_modules|tests|coverage|website|mcp|docs|tools|\.github|\.git|\.verify|\.playwright-mcp)/|(^|/)(eslint\.config\.js|vitest\.config\.js|package-lock\.json|package\.json)$'
if grep -E "$forbidden" /tmp/repolens-zip-files.txt; then
echo "Release zip contains forbidden dev/test files" >&2
exit 1
fi
for required in manifest.json src/background.js output-tab.html library.html whats-new.html src/store/idb.js icons/icon128.png; do
if ! grep -Fxq "$required" /tmp/repolens-zip-files.txt; then
echo "Release zip missing required file: $required" >&2
exit 1
fi
done
run: npm run package
- uses: actions/upload-artifact@v4
with:
name: repolens-extension-zip
path: dist/*.zip
path: dist/
if-no-files-found: error
- name: Publish GitHub Release
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ github.token }}
run: |
node tools/check-release.mjs --notes > release-notes.md
gh release create "$GITHUB_REF_NAME" dist/*.zip dist/*.sha256 \
--title "RepoLens $GITHUB_REF_NAME" \
--notes-file release-notes.md \
--verify-tag
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ coverage/
# playwright/chrome-devtools MCP scratch output
.playwright-mcp/
*-screenshot.png
dist/
.store-assets-profile/
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ package-lock.json
*.min.js
icons
vendor
dist
36 changes: 29 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,48 @@ This project follows [Semantic Versioning](https://semver.org/) and groups chang
by theme. Dates are when the release landed on `main`. 1.1.0 through 1.6.0 shipped
the same day, as a rapid burst of improvements, so they share a date.

## [Unreleased] — 2026-06-25 · _Subscription Auth Fix · Codex Responses API_
## [3.2.0] — 2026-10-13 · _Store Ready · Review Queue · Free Scans_

The first build packaged for the Chrome Web Store, plus the review and free-model
work that landed after 3.1.1.

### Added

- **Review Queue in Library.** One toggle narrows the Library to pending **Trial** / **Hold** adoption decisions and scans older than 14 days. It composes with existing search, collections, decisions, exports, and sorts, making a periodic review session a focused list instead of a manual hunt.
- **Cost-aware model defaults.** New OpenRouter connections start on `z-ai/glm-5.2:free`; local Ollama starts on `llama3.2:3b`, a reliable JSON-capable local model. Qwen3 4B/8B remain available for deeper local reasoning. Existing saved model selections are unchanged.
- **Chrome Web Store package.** `npm run package` builds `dist/repolens-v<version>.zip` deterministically (sorted entries, pinned timestamps, byte-identical rebuilds) with a `.sha256`, and refuses to build if the package would ship a broken page: inline scripts or handlers the MV3 CSP blocks, imports or page URLs that don't resolve, wrong icon sizes, manifest store limits, dev files, or a permission without a store justification. Tag pushes attach the zip to a GitHub Release.
- **Release consistency gate.** `npm run check:release` fails when the manifest, package, lockfile, CHANGELOG, What's New, README, or website disagree on the current version.
- **Privacy policy and store listing kit.** A plain-language [privacy policy](PRIVACY.md) (also on the website) and `docs/store/chrome-web-store.md` with listing copy, permission justifications, data-use disclosures, and the asset checklist.
- **MCP: goal-aware evaluation and product analysis.** `evaluate_for_goal` scores a repo against a concrete goal, and `analyze_product` inspects a public product URL (private and loopback hosts are refused).

### Changed

- **Fewer permissions.** RepoLens no longer requests `tabs` or `webNavigation`, so the install prompt no longer says it can read your browsing history. Unused host access to `claude.ai`, `grok.com`, and the redundant `pypi.org/pypi/*` was removed. ChatGPT sign-in now reads its loopback callback through `tabs.onUpdated` and the existing `localhost` permission.
- **Website install path is honest.** "Add to Chrome" buttons now say **Install for Chrome** and lead to a step-by-step install guide (release zip or source) until the Web Store listing is live; one setting flips them to the store link.

### Fixed

- **Library page loads again.** It imported its animation helpers from `src/vendor/`, which hasn't existed since the source tree moved into `src/`, so the whole Library script failed to load. It now imports from `vendor/`.
- **Share card and What's New pages run their scripts.** Both used inline `<script>` blocks, which the MV3 content security policy blocks: the share card stuck on "Loading…" and What's New stayed blank below the hero. The scripts now live in `src/share.js` and `src/whats-new.js`.
- **Pressing Enter in Ask your library no longer reloads the page.** The form relied on an inline `onsubmit` handler that the CSP also blocks.
- **Local Ollama 403s say how to fix them.** Ollama refuses `chrome-extension://` origins unless `OLLAMA_ORIGINS` allows them, which surfaced as a misleading "credential was rejected". The error now names the `OLLAMA_ORIGINS=chrome-extension://*` fix, and the install and model docs include it.
- **MCP product-page SSRF guard rejects IPv6 loopback correctly.** URL hostnames are normalized before IP classification, so `http://[::1]/` cannot fall through to DNS resolution.
- **Root and MCP dependency audits are clean.** Updated lockfiles resolve the reported high/moderate advisories.
- **Root and MCP dependency audits are clean.** Updated lockfiles resolve the reported high/moderate advisories, including the ones published since (brace-expansion, source-map-js, the MCP SDK, and proxy-addr).
- **OpenRouter OAuth now preserves its PKCE verifier and callback target in session storage.** The connection flow uses the extension-specific callback path, validates the returned callback before exchanging the one-time code, surfaces the provider's authorization error, and clears the one-time state on every outcome.
- **Free OpenRouter scans no longer use the generic free router.** Its provider-selected models can return comma-separated JSON fragments, which made otherwise successful scans fail in the response parser. RepoLens now defaults to `z-ai/glm-5.2:free`, whose current OpenRouter capabilities include JSON response format support, sends JSON-object mode for that model, keeps the generic router migrated to the reliable default, and constrains scan replies to fit within the available output budget.
- **Long structured OpenRouter responses no longer fail as malformed JSON.** The scan completion ceiling is now 8,192 tokens (was 4,096), and a provider `finish_reason: "length"` is reported as an actionable limit error rather than passed to the JSON parser as a partial object.
- **OpenRouter model picker is now genuinely free-only.** It rebuilds from the live catalog using zero prompt/completion pricing, text-only output, and JSON response-format support; paid, audio-capable, generic-router, and stale saved selections are removed from the picker.

---

## [3.1.1] — 2026-06-25 · _Subscription Auth Fix · Codex Responses API_

### Fixed

- **ChatGPT subscription sign-in now works without API platform access.** The previous flow tried to mint an OpenAI API key (`sk-…`) from the OAuth `id_token` via a token-exchange grant — which only works if your ChatGPT plan includes API platform access. Most ChatGPT Plus/Pro subscriptions don't, so every sign-in failed with "Couldn't enable API access for this ChatGPT account." RepoLens now uses the **Codex Responses API** at `chatgpt.com/backend-api/codex/responses` with the OAuth access token directly — the same endpoint the Codex CLI and Aside browser use. No API key minting required; the subscription itself authorizes the request.
- **ChatGPT OAuth error messages are now readable.** The token-exchange error handler was stringifying nested error objects as `[object Object]`. It now drills into `error.message`, `error_description`, and stringifies objects properly.
- **ChatGPT OAuth callback no longer clears credentials on success.** The callback handler was still calling `mintOpenAIApiKey()` after the code exchange, which failed and then wiped the just-stored OAuth credentials — making the provider look disconnected even though the sign-in itself succeeded.
- **Claude sign-in rate-limit handling.** Anthropic's token endpoint rate-limits aggressively on repeated sign-in attempts or concurrent refreshes, surfacing as "Claude token exchange failed: Rate limited." Both the token exchange and refresh paths now retry with exponential backoff (up to 2 retries, reads `retry-after` header, 30s max delay) before giving up.
- **OpenRouter OAuth now preserves its PKCE verifier and callback target in session storage.** The connection flow uses the extension-specific callback path, validates the returned callback before exchanging the one-time code, surfaces the provider's authorization error, and clears the one-time state on every outcome.
- **Free OpenRouter scans no longer use the generic free router.** Its provider-selected models can return comma-separated JSON fragments, which made otherwise successful scans fail in the response parser. RepoLens now defaults to `z-ai/glm-5.2:free`, whose current OpenRouter capabilities include JSON response format support, sends JSON-object mode for that model, keeps the generic router migrated to the reliable default, and constrains scan replies to fit within the available output budget.
- **Long structured OpenRouter responses no longer fail as malformed JSON.** The scan completion ceiling is now 8,192 tokens (was 4,096), and a provider `finish_reason: "length"` is reported as an actionable limit error rather than passed to the JSON parser as a partial object.
- **OpenRouter model picker is now genuinely free-only.** It rebuilds from the live catalog using zero prompt/completion pricing, text-only output, and JSON response-format support; paid, audio-capable, generic-router, and stale saved selections are removed from the picker.

### Changed

Expand All @@ -37,7 +59,7 @@ the same day, as a rapid burst of improvements, so they share a date.

---

## [Unreleased] — 2026-06-19 · _Actionable Scans · Smooth Loading · Provider Refresh · Stability_
## [3.1.0] — 2026-06-19 · _Actionable Scans · Smooth Loading · Provider Refresh · Stability_

### Added

Expand Down
71 changes: 71 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# RepoLens privacy policy

_Last updated: 2026-10-13 · applies to the RepoLens Chrome extension 3.2.0 and later_

RepoLens is a Chrome extension that reads public code repositories and asks an AI
model you choose to explain them. There is no RepoLens server and no RepoLens
account. The developer receives none of your data.

## What RepoLens stores, and where

Everything stays in your browser profile on your device:

- **Settings and credentials** in `chrome.storage.local`: your theme, writing
voice, model choices, API keys, and the OAuth tokens for providers you sign in
to.
- **Scan state** in `chrome.storage.session`: progress of scans that are
running. Chrome clears it when the browser closes.
- **Your library** in IndexedDB: saved analyses, notes, decisions, evaluations,
collections, canvas layouts, and a scan cache.

Removing the extension deletes all of it. **Library → Clear** deletes the library
earlier if you want. **Library → Export** and **Options → Back up your settings**
write files only when you ask for them, and settings backups never include API
keys or OAuth tokens.

## What leaves your browser, and why

RepoLens sends data only when you start something that needs it, and only to the
service doing that job:

| When | Sent to | What is sent |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| You scan a repo or package | GitHub, GitLab, npm, or PyPI public APIs | The repo or package name, to fetch its public metadata, README, and file list. A GitHub token is sent only if you add one in Options. |
| A scan, lens, Ask, or Deep Dive runs | The AI provider you connected (for example Anthropic, OpenAI, Google, OpenRouter, or a local Ollama) | The public repo content above plus RepoLens's prompt, authenticated with your key or OAuth token. Your provider's own privacy policy covers what it does with requests. |
| You sign in with Claude, ChatGPT, Grok, or OpenRouter | That provider's sign-in service | The standard OAuth authorization exchange. |
| You use Discover | GitHub search API | Your search terms. |
| You run the optional deeper-scan runner or local Ollama | Your own machine (`localhost`) | The repo URL or prompt. Nothing goes to the internet unless the runner downloads public source. |
| You add a custom endpoint or import from VelesDB | The address you entered, after Chrome asks for your permission | The same request a built-in provider would get, or the import request. |

RepoLens has a content script on GitHub, GitLab, npm, and PyPI pages. It only
tells the extension that the current tab is a repo page, so the toolbar icon can
light up. It does not read or send page content.

Share cards put the verdict summary in the link itself (after `#`). Nothing is
uploaded; whoever you send the link to sees what the link contains.

## What RepoLens does not do

- No analytics, telemetry, crash reporting, ads, or tracking.
- No selling, renting, or transferring of data to anyone.
- No use of your data for anything other than the feature you started.
- No reading of your browsing history. RepoLens does not request the `tabs`,
`history`, or `webNavigation` permissions.
- No remotely hosted code. Everything RepoLens runs ships inside the extension.

RepoLens's use of information complies with the
[Chrome Web Store User Data Policy](https://developer.chrome.com/docs/webstore/program-policies/user-data-faq),
including the Limited Use requirements.

## The RepoLens MCP server

The separate `repolens-mcp` server runs on your computer when you start it. It
calls the same public repository APIs, the public page you pass to
`analyze_product`, and the AI provider whose key you set in its environment, and writes reports to local files. It sends nothing to the
developer either.

## Questions and changes

Open an issue at <https://github.com/New1Direction/RepoLens/issues>. Changes to
this policy are recorded in the repository history and the
[changelog](CHANGELOG.md).
Loading
Loading