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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ jobs:
- name: PR policy self-test
run: npm run check:pr-policy

- name: Codebase index coverage
run: npm run docs:check-index

- name: Dependency and duplicate-export hygiene
run: npm run check:knip

- name: Maintainability hotspot budgets
run: npm run check:maintainability-budgets

- name: Format check
run: npm run format:check

Expand Down
7 changes: 6 additions & 1 deletion docs/codebase-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ Structured map for AI agents and onboarding. For live routes, see `docs/site-map
| `/differentials`, `/diagnoses`, `/presentations` | `src/app/differentials/` |
| `/dsm`, `/dsm/search`, `/dsm/compare`, `/dsm/diagnoses/[slug]` | `src/app/dsm/` |
| `/documents/search`, `/source`, `/evidence`, `/[id]` | `src/app/documents/` |
| `/factsheets`, `/factsheets/search`, `/factsheets/[slug]` | `src/app/factsheets/` |
| `/favourites` | `src/app/favourites/page.tsx` |
| `/forms`, `/forms/[slug]` | `src/app/forms/` |
| `/medications`, `/medications/[slug]` | `src/app/medications/` |
| `/privacy` | `src/app/privacy/page.tsx` |
| `/reference/colour-coding` | `src/app/reference/` |
| `/services`, `/services/[slug]` | `src/app/services/` |
| `/therapy-compass` | `src/app/therapy-compass/` |
| `/tools` | `src/app/tools/` |
| `/specifiers`, `/specifiers/[slug]`, `/specifiers/builder`, `/specifiers/compare`, `/specifiers/map` | `src/app/specifiers/` |
| `/formulation`, `/formulation/[slug]`, `/formulation/builder`, `/formulation/compare`, `/formulation/map` | `src/app/formulation/` |
| `/mockups/*` | `src/app/mockups/` (404 in production) |
Expand All @@ -70,6 +74,7 @@ Structured map for AI agents and onboarding. For live routes, see `docs/site-map

| Area | Routes | Entry files |
| ------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Account | `/api/account/favourites`, `/api/account/preferences` | `account/` |
| Answers | `/api/answer`, `/api/answer/stream`, `/api/answer-feedback` | `answer/route.ts`, `answer/stream/route.ts`, `answer-feedback/` |
| Search | `/api/search`, `/api/search/interaction`, `/api/search/universal` | `search/` |
| Upload | `/api/upload` | `upload/route.ts` |
Expand Down Expand Up @@ -158,7 +163,7 @@ Structured map for AI agents and onboarding. For live routes, see `docs/site-map

### Schema tables

`documents`, `document_pages`, `document_images`, `document_chunks`, `document_embedding_fields`, `document_index_units`, `document_table_facts`, `document_labels`, `document_summaries`, `document_sections`, `document_memory_cards`, `document_index_quality`, `document_title_words`, `ingestion_jobs`, `ingestion_job_stages`, `indexing_v3_agent_jobs`, `import_batches`, `image_caption_cache`, `rag_queries`, `rag_query_misses`, `rag_aliases`, `rag_response_cache`, `rag_retrieval_logs`, `rag_visual_eval_cases`, `rag_visual_eval_runs`, `rag_answer_feedback`, `clinical_registry_records`, `clinical_registry_record_sources`, `medication_records`, `differential_records`, `source_review_events`, `api_rate_limits`, `api_rate_limit_subjects`, `audit_logs`, `storage_cleanup_jobs`
`documents`, `document_pages`, `document_images`, `document_chunks`, `document_embedding_fields`, `document_index_units`, `document_table_facts`, `document_labels`, `document_summaries`, `document_sections`, `document_memory_cards`, `document_index_quality`, `document_title_words`, `document_publication_approvals`, `ingestion_jobs`, `ingestion_job_stages`, `indexing_v3_agent_jobs`, `import_batches`, `image_caption_cache`, `rag_queries`, `rag_query_misses`, `rag_aliases`, `rag_response_cache`, `rag_retrieval_logs`, `rag_visual_eval_cases`, `rag_visual_eval_runs`, `rag_answer_feedback`, `clinical_registry_records`, `clinical_registry_record_sources`, `medication_records`, `differential_records`, `source_review_events`, `user_favourites`, `user_preferences`, `api_rate_limits`, `api_rate_limit_subjects`, `audit_logs`, `storage_cleanup_jobs`

**Storage buckets:** `clinical-documents`, `clinical-images` (private)

Expand Down
8 changes: 8 additions & 0 deletions docs/process-hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ This document turns the current process review into phased, durable repo practic
- **Provider-gated RAG safety ideas:** the same stale worktree contained conservative answer-quality thresholds, an evaluation cost-cap preflight, production-safety validation, deep-health assessment, and citation/vector proof tests. Its 754-line retrieval migration and route changes conflict with the later public-title privacy and migration chain and must not be replayed. If explicitly approved, rescope only the still-relevant preflight utilities and tests against current `main`; keep live OpenAI/Supabase validation separate.
- **Semantic reranking rollout debt:** PR #901 keeps `RAG_SEMANTIC_RERANK_ENABLED=false`. Do not enable it until the provider-backed 36/36 retrieval-quality gate and an ambiguity-focused canary are explicitly approved and recorded.

## P3 residual-debt controls (2026-07-19)

- `docs:check-index` is now a local and required static-PR guard; product-route and schema-table additions must update `docs/codebase-index.md` in the same change.
- `check:knip` blocks unused direct dependencies, unlisted or unresolved imports, and duplicate exports. Playwright's plugin is disabled because Knip already treats every `tests/**/*.spec.ts` file and both config files as entries, while executing the protected configs outside the repository runner is intentionally forbidden. `check:knip:exports` preserves the current 230 unused-export and 116 unused-type findings as advisory triage; do not bulk-delete those symbols without caller and runtime evidence.
- Unit coverage inventories pages/layouts, mockups, scripts, the TypeScript worker, and Supabase Edge Functions. The historical core-source threshold remains unchanged and scoped to its previous file set, so the broader zero-coverage inventory cannot dilute the established floor.
- The required Chromium PR job also runs the dashboard/document visual-artifact smoke. Quarantined and mockup journeys remain advisory, while full cross-browser coverage remains in the release matrix.
- `check:maintainability-budgets` prevents the four current hotspots from growing beyond their post-remediation 2026-07-19 baselines: `ClinicalDashboard.tsx` 4,272 lines, `rag.ts` 5,238, `DocumentViewer.tsx` 3,166, and `indexing-v3-agent/index.ts` 2,191. A budget breach must be handled by a cohesive, separately verified extraction; do not raise a ceiling to land unrelated behavior.

## Staging tenancy evidence

The provider-backed A/B tenancy regression is intentionally outside local and PR
Expand Down
4 changes: 2 additions & 2 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Ordinary Vitest and Playwright runs remove OpenAI, Supabase, database, and E2E c
| `npm run test:focused -- --files <paths>` | Local iteration using Vitest related-file selection. It fails closed for deleted files, test infrastructure, configuration, or an empty/unsafe mapping. |
| `npm run test` | Complete offline unit suite. |
| `npm run test:live` | Explicit provider suite; requires `ALLOW_PROVIDER_TESTS=true`. |
| `npm run test:e2e:pr` | Required production Chromium journeys, excluding mockups and quarantined tests. |
| `npm run test:e2e:pr` | Required production Chromium journeys and visual-artifact smoke, excluding mockups and quarantined tests. |
| `npm run test:e2e:advisory` | Quarantined and mockup journeys in one advisory invocation. |
| `npm run verify:cheap` | Broad offline local gate: runtime/config checks, lint, typecheck, and the full unit suite. |
| `npm run verify:pr-local` | PR-like local gate. Formatting is checked on the changed set, the full unit suite runs once, and RAG scope adds fixture/manifest validation. |
Expand All @@ -35,4 +35,4 @@ Blocking tests run with zero retries. CI publishes list, JUnit, and JSON reports

## CI topology

PR CI keeps static checks separate from one required full unit run with coverage. UI scope uses one required production Chromium invocation for non-quarantined critical and regression journeys, plus one advisory invocation for quarantined and mockup journeys. Build, migration, security, and release behavior remain independently scoped and unchanged.
PR CI keeps static checks separate from one required full unit run with coverage. UI scope uses one required production Chromium invocation for non-quarantined critical, regression, and dashboard/document visual-artifact journeys, plus one advisory invocation for quarantined and mockup journeys. Build, migration, security, and release behavior remain independently scoped and unchanged.
5 changes: 2 additions & 3 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
],
"project": ["src/**/*.{ts,tsx}", "scripts/**/*.{ts,mjs,cjs}", "worker/**/*.ts", "eslint-rules/**/*.mjs"],
"ignore": ["src/lib/supabase/database.types.ts", "**/*mockup*", "supabase/functions/**"],
"ignoreDependencies": ["tailwindcss", "server-only"],
"ignoreBinaries": ["railway", "taskkill"],
"ignoreDependencies": ["tailwindcss"],
"vitest": { "config": ["vitest.config.mts"] },
"playwright": { "config": ["playwright.config.ts", "playwright.visual.config.ts"] }
"playwright": false
}
20 changes: 0 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@
"test:e2e:visual": "node scripts/run-playwright.mjs --config=playwright.visual.config.ts",
"test:cross-tenant:staging": "node scripts/run-tsx.mjs scripts/test-cross-tenant-staging.ts",
"verify:cheap": "node scripts/run-heavy.mjs --npm-script verify:cheap:internal",
"verify:cheap:internal": "npm run check:runtime && npm run check:github-actions && npm run check:ci-scope && npm run check:ci-triage && npm run check:pr-policy && npm run sitemap:check && npm run brand:check && npm run check:therapy-data-index && npm run check:type-scale && npm run check:icon-scale && npm run check:design-system-contract && npm run check:function-grants && npm run check:owner-scope && npm run lint && npm run typecheck && npm run test",
"verify:cheap:internal": "npm run check:runtime && npm run check:github-actions && npm run check:ci-scope && npm run check:ci-triage && npm run check:pr-policy && npm run sitemap:check && npm run docs:check-index && npm run check:knip && npm run check:maintainability-budgets && npm run brand:check && npm run check:therapy-data-index && npm run check:type-scale && npm run check:icon-scale && npm run check:design-system-contract && npm run check:function-grants && npm run check:owner-scope && npm run lint && npm run typecheck && npm run test",
"verify:pr-local": "node scripts/verify-pr-local.mjs",
"verify:ui": "npm run check:runtime && npm run test:e2e:pr",
"verify:release": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:e2e && npm run check:production-readiness && npm run governance:release && npm run eval:quality:release",
"verify:release:offline": "node scripts/verify-release-offline.mjs",
"ci:env-check": "node scripts/check-ci-env.mjs",
"check:github-actions": "node scripts/check-github-action-pins.mjs",
"check:client-bundle-secrets": "node scripts/check-client-bundle-secrets.mjs",
"check:knip": "knip",
"check:knip": "knip --no-progress --include dependencies,unlisted,unresolved,duplicates",
"check:knip:exports": "knip --no-progress --exports --no-exit-code",
"check:maintainability-budgets": "node scripts/check-maintainability-budgets.mjs",
"check:ci-scope": "node scripts/ci-change-scope.mjs --self-test",
"check:ci-triage": "node scripts/ci-triage.mjs --self-test",
"check:pr-policy": "node scripts/pr-policy.mjs --self-test",
Expand Down Expand Up @@ -174,7 +176,6 @@
},
"dependencies": {
"@next/env": "16.2.10",
"@supabase/realtime-js": "^2.110.7",
"@supabase/ssr": "^0.12.0",
"@supabase/supabase-js": "^2.108.2",
"exceljs": "^4.4.0",
Expand Down
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ const chromiumExecutablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
// they share a spec file. Every required browser project uses the same
// production matcher and tag exclusion.
const productionSpecPattern =
/.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|tools|overlap|universal-search|specifiers|formulation|pwa|route-coverage))\.spec\.ts/;
/.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|tools|overlap|universal-search|specifiers|formulation|pwa|route-coverage|visual-artifacts))\.spec\.ts/;
const mockupSpecPattern = /.*ui-(tools|tools-collapse|tools-task-directory)\.spec\.ts/;
const mockupTag = /@mockup/;

export default defineConfig({
testDir: "./tests",
testMatch:
/.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|tools|tools-collapse|tools-task-directory|overlap|universal-search|specifiers|formulation|pwa|route-coverage))\.spec\.ts/,
/.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|tools|tools-collapse|tools-task-directory|overlap|universal-search|specifiers|formulation|pwa|route-coverage|visual-artifacts))\.spec\.ts/,
timeout: 60_000,
retries: 0,
// Fail the run if a stray `test.only` is committed: otherwise it silently
Expand Down
30 changes: 30 additions & 0 deletions scripts/check-maintainability-budgets.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";

const budgets = new Map([
["src/components/ClinicalDashboard.tsx", 4272],
["src/lib/rag.ts", 5238],
["src/components/DocumentViewer.tsx", 3166],
["supabase/functions/indexing-v3-agent/index.ts", 2191],
]);

function sourceLineCount(file) {
const source = readFileSync(file, "utf8").replaceAll("\r\n", "\n");
const lines = source.split("\n");
return source.endsWith("\n") ? lines.length - 1 : lines.length;
}

const failures = [];
for (const [file, maximum] of budgets) {
const actual = sourceLineCount(file);
if (actual > maximum) failures.push(`${file}: ${actual} lines exceeds the ${maximum}-line no-growth budget`);
else console.log(`[maintainability] ${file}: ${actual}/${maximum} lines`);
}

if (failures.length) {
console.error("Maintainability hotspot budget exceeded. Extract a cohesive module instead of growing the monolith:");
for (const failure of failures) console.error(` ${failure}`);
process.exit(1);
}

console.log("Maintainability hotspot budgets passed.");
3 changes: 1 addition & 2 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ import {
type DocumentDrawerStatusFilter,
type DocumentPagination,
type LabelReviewMutationBody,
recentQueryStorageKey,
} from "@/components/clinical-dashboard/dashboard-contracts";

const DifferentialsHome = dynamic(
Expand Down Expand Up @@ -171,7 +170,7 @@ const DocumentSearchResultsPanel = dynamic(
{ ssr: false },
);

import { clearLegacyRecentQueries, demoRecentQueryOwnerId } from "@/components/clinical-dashboard/recent-query-storage";
import { clearLegacyRecentQueries, demoRecentQueryOwnerId, recentQueryStorageKey } from "@/lib/recent-query-storage";
import type { SearchFacets } from "@/components/clinical-dashboard/document-search-results";
import { isWeakRelevance } from "@/components/clinical-dashboard/relevance";
import {
Expand Down
2 changes: 0 additions & 2 deletions src/components/clinical-dashboard/dashboard-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ export const navigationHashes = ["#search", "#quotes", "#images", "#sources"] as
export const mobileSectionFabMediaQuery =
"(max-width: 768px), ((max-width: 1023px) and (hover: none) and (pointer: coarse))";

export const recentQueryStorageKey = "clinical-kb-recent-queries";

export type DocumentPagination = {
limit: number;
offset: number;
Expand Down
6 changes: 1 addition & 5 deletions src/components/clinical-dashboard/global-search-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ import {

import { AccountSetupDialog } from "@/components/clinical-dashboard/account-setup-dialog";
import { ClinicalDashboard } from "@/components/ClinicalDashboard";
import {
clearLegacyRecentQueries,
demoRecentQueryOwnerId,
loadRecentQueries,
} from "@/components/clinical-dashboard/recent-query-storage";
import { clearLegacyRecentQueries, demoRecentQueryOwnerId, loadRecentQueries } from "@/lib/recent-query-storage";
import { PatientProfileProvider } from "@/components/clinical-dashboard/patient-profile-context";
import { SearchCommandProvider } from "@/components/clinical-dashboard/search-command-context";
import { SettingsDialog } from "@/components/clinical-dashboard/settings-dialog";
Expand Down
2 changes: 1 addition & 1 deletion src/components/clinical-dashboard/settings-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
POPULATION_OPTIONS,
useAppPreferences,
} from "@/components/clinical-dashboard/use-app-preferences";
import { clearRecentQueries, countRecentQueries } from "@/components/clinical-dashboard/recent-query-storage";
import { clearRecentQueries, countRecentQueries } from "@/lib/recent-query-storage";
import {
cn,
fieldControlWithIcon,
Expand Down
Loading
Loading