- Chrome (any recent version supporting MV3)
- Node.js 18+ (for running tests — not needed for the extension itself)
- Loading the extension and running Node tests require no dependency install or bundler. Packaging uses Node and the system
ziputility. The separate browser suite needs npm dependencies and Playwright's Chromium.
- Open Chrome →
chrome://extensions - Enable Developer mode (toggle, top right)
- Click Load unpacked
- Select the repo folder (
/path/to/IntentLock) - Open a new tab — the onboarding wizard appears
| What changed | How to reload |
|---|---|
background.js or any imported module |
Extensions page → refresh icon on the IntentLock card |
content.js, page-tracker.js, intervention-overlay.js |
Extensions page refresh + reload the affected tab |
manifest.json |
Extensions page refresh (same as above) |
newtab.js, onboarding.js, options.js, popup.js, analytics.js |
Close and reopen the page/popup |
heuristic-policy.js |
Extensions page refresh (background imports it) |
The reload button is the circular arrow (↻) under the extension card in chrome://extensions.
The extension is loaded from the repo directly. If you keep a separate "loadable" copy at a different path (e.g. /Users/you/Documents/Intentlock), sync it manually:
rsync -a --exclude='.git' --exclude='node_modules' \
/path/to/IntentLock/ /path/to/Intentlock/Or just point Chrome at the repo folder and avoid the sync entirely.
# Single suite
node --test tests/heuristic-policy.test.mjs
# All dependency-free Node suites (not browser journeys)
npm test
# Verbose (shows individual test names)
node --test --test-reporter=spec tests/*.test.mjs
# Manifest, runtime reference, workflow, packaging, and version checks
npm run verify:static
npm run validate:version -- v1.6.0
# Deterministic release artifact (writes to dist/ by default)
npm run packageThe Node suites use built-in node:test + assert/strict; npm test needs no
dependency install and does not discover browser tests. CI (.github/workflows/test.yml)
runs lint and npm audit (quality job), npm test, npm run verify:static, version validation and a package build on
Node 18/20/22, plus a separate browser job that installs the pinned Playwright and
Chromium and runs the real journeys with the browser sandbox enabled. Locally, run the
real browser suite separately:
npm ci
npx playwright install chromium
npm run test:browserSee browser journeys for the pinned browser, isolation, actual coverage, historical results and setup failures. See manual acceptance for 1.6.0 for lifecycle, real-time and accessibility checks, and pilot preparation for the voluntary seven-day protocol. Neither automated success nor preparation means a pilot ran.
The release ZIP is built from an explicit runtime allowlist with stable entry order and timestamps. It excludes tests, docs, source plans, package metadata, and development helpers. GitHub Actions validates the tag against manifest.json before attaching the ZIP to a GitHub Release.
| File | What it covers |
|---|---|
heuristic-policy.test.mjs |
Intent classification, site lookup, policy builders, drift scoring, migration |
background.test.mjs |
Service worker helper functions |
drift.test.mjs |
Legacy heuristic drift scoring |
drift-cache.test.mjs |
TTL cache hit/miss/eviction |
drift-threshold.test.mjs |
Score threshold boundary conditions |
cooldown.test.mjs |
Per-domain override cooldown logic |
llm-backoff.test.mjs |
Quota backoff state machine |
llm.test.mjs |
LLM call mocking and response parsing |
providers.test.mjs |
Provider config validation, API key checks |
page-tracker.test.mjs |
Dwell accumulation, SPA navigation detection |
error-log.test.mjs |
Error classification, log rotation |
intervention-overlay.test.mjs |
Shadow DOM overlay construction |
distraction-sites.test.mjs |
Legacy default domain list |
static-smoke.test.mjs |
Manifest integrity, referenced assets exist, minimum permissions |
IntentLock/
├── manifest.json # MV3 manifest
├── background.js # Service worker — session, drift, intervention
├── content.js # Injected into every page
├── page-tracker.js # Dwell time + SPA detection (used by content.js)
├── intervention-overlay.js # Shadow-DOM overlay (used by content.js)
├── heuristic-policy.js # Policy engine — pure, node-testable
├── drift.js # Legacy heuristic constants + evaluateHeuristicDrift
├── drift-cache.js # In-memory TTL cache for LLM results
├── llm.js # LLM drift check
├── llm-backoff.js # Quota/rate-limit backoff guard
├── providers.js # Multi-provider LLM abstraction
├── privacy-utils.js # URL minimization, retention, and secret redaction
├── distraction-sites.js # Legacy 8-domain default list
├── error-log.js # Diagnostic log (chrome.storage.local)
├── newtab.html / newtab.js # New tab override — session form + active session
├── onboarding.js # First-run wizard (imported by newtab.js)
├── newtab.css # Shared styles (used by newtab + options + analytics)
├── options.html / options.js # Settings page
├── popup.html / popup.js # Toolbar popup (compact summary)
├── analytics.html / analytics.js # Week glance dashboard
├── intervention.html / .js # Tab-replacement intervention page
├── diagnostics.html / .js # Error log viewer
├── history.html / .js # Session history viewer
├── icon.svg / icon*.png # Extension icons
├── CHANGELOG.md
├── tests/ # Node suites; browser/ contains Playwright journeys
└── docs/ # This documentation
├── architecture.md
├── heuristic-policy.md
├── storage-schema.md
└── development.md
- Append an entry to
SITE_CATEGORIESinheuristic-policy.js:{ id: 'my_category', label: 'My Category', description: 'One line description', defaultPolicy: 'warn', domains: ['example.com', 'another.com'], }
- Add it to all three strictness presets in
STRICTNESS_PRESETS - If relevant, add it to
CATEGORY_ALIGNMENTfor the intent categories that align with it - Add a test in
tests/heuristic-policy.test.mjs - Run
node --test tests/heuristic-policy.test.mjs
No other files need to change — the settings grid and policy schema pick it up automatically.
- Append an entry to
INTENT_CATEGORIESinheuristic-policy.js - Add it to
CATEGORY_ALIGNMENTwith its aligned site category IDs - Add keyword classification tests in
tests/heuristic-policy.test.mjs
- Add an entry to
PROVIDERSinproviders.js:my_provider: { id: 'my_provider', label: 'My Provider', apiStyle: 'openai', // 'openai' | 'gemini' | 'ollama' defaultModel: 'my-model', defaultBaseUrl: 'https://api.example.com/v1/chat/completions', authType: 'bearer', isLocal: false, }
- Add it to the
<select>inoptions.htmlif it needs a dedicated UI entry (custom provider flow handles most cases already) - Add provider validation tests in
tests/providers.test.mjs
These notes describe current 1.6.0 source, not a completed security audit. Earlier 1.5.1 remediation work was not all merged; do not inherit its guarantees.
- The manifest permits packaged scripts and broad HTTP/HTTPS page access. Optional AI performs inference through the configured provider and can send declared intent and browsing context off-device. The pilot adds no telemetry and requires AI to remain unconfigured in a fresh profile.
llm.jsminimizes browsing URLs to origins in drift prompts. This does not anonymize declared intent or other free text. Do not use real secrets in QA.- Settings writes keys to
chrome.storage.sessionwhen available, with a local fallback otherwise.providers.jscan also read legacy local key aliases even when session storage exists. Session-only key storage is not an absolute guarantee. - Built-in provider endpoints are validated; custom HTTP is restricted to loopback and custom cloud endpoints require HTTPS. Use a non-forwarding local fake provider for QA, never a live cloud account or real API key.
- Tracking opt-out is intended to stop new event/drift evaluation and calls; the browser suite covers overlay removal and no appended events after opt-out. In-flight provider work and storage/deletion races need the manual checks.
- Local active-session events/lock state can contain URLs. History sanitization removes event arrays and normalizes selected URL fields, but retains intent, reflections and hostnames. Secret-pattern redaction is not anonymization.
- Retention helpers use 30 days/100 completed sessions and 14 days/200 diagnostic entries. Filtering a view is not proof that expired storage was erased on every path or on a continuous timer. Verify both storage and views manually.
- Delete all data requests worker-owned local/session clearing. The automated journey verifies deletion after tracking opt-out and page reload; it does not prove durability through all queued writes, migrations, provider responses or browser restarts. Do not claim deletion is universally final without evidence.
Intervention not firing?
- Open
chrome://extensions→ IntentLock → Service Worker → Inspect → Console - Check
evaluateDriftlogs — look for cooldown or debounce skips - Try Settings → Test intervention to confirm the pipeline works end-to-end
Overlay not showing?
- The content script may not be injected (Chrome/extension pages,
file://, some CSP-strict sites) - Check the tab's console for
content.jserrors - Background falls back to tab replacement (
intervention.html) if the content script doesn't respond
LLM not triggering?
- Settings → LLM provider — confirm provider + key are saved
- Settings → Diagnostics — check for
api_error,quota_exceeded,invalid_api_key - The
drift-cache.jscaches results for 60 seconds; wait or reload the session to force a fresh check
Policy not loading?
- Open Service Worker console →
chrome.storage.local.get(['heuristicPolicy'], console.log) - If absent,
loadConfig()will fall back todeep_work / balancedand log to the error log