Promote dev to main - #430
Merged
Merged
Conversation
…ency-graph feat(dependencies): serve the dependency graph from the sealed ri.v1 snapshot
) data_source was written as the literal "real" on every repository and never derived from any actual distinction, so it presented a hardcoded value as a computed one. Removed the column (reversible migration 0007), the schema field, and both service write sites; the API no longer returns it. data_source is unrelated to DataSourceBadge, which renders the real 'source: upload|github' origin field and is left untouched.
Removes the DataSource type, Repository.dataSource / RepositoryResponse.dataSource, the backend.ts passthrough, and the hardcoded dataSource: 'real' literal in useAnalysisPipeline.ts, matching the backend field removal. DataSourceBadge is unaffected -- it renders the separate, real 'source' (upload/github) field.
--reload-dir app scopes the watcher to apps/backend/app instead of the whole working directory, so writes under .local/storage during ingestion and analysis no longer trigger a mid-job server restart that drops open connections.
…lysis failed (#164) useAnalysisPipeline's status poll caught every thrown error identically, rendering a single dropped NetworkError/TimeoutError as the terminal 'Analysis Failed' state even though the durable job survives server-side. Distinguishes transport errors from a real API-reported job failure: a NetworkError/TimeoutError now retries with exponential backoff (1s/2s/4s/8s/ 15s, 5 attempts) behind a new, non-terminal 'retrying' connection state; retry exhaustion surfaces a distinct 'connection lost' state with a manual retry action, never the Analysis Failed banner. A real status: 'failed' response still renders immediately, unaffected. Progress is never advanced or reset while retrying -- only the last known real state is shown.
…alues fix(honesty): remove data_source placeholder and disclose chunked AI delivery
…ad-watcher chore(dev): restrict uvicorn reload watcher to backend source (Closes #161)
…status-polling fix(analysis): retry transient poll failures instead of declaring analysis failed (Closes #164)
) AUTO_CREATE_TABLES's create_all creates missing tables but never alters an existing one and never advances the alembic_version stamp, so an existing local dev database silently drifts from the code after a schema-changing merge -- requests fail with opaque IntegrityError/OperationalError 500s instead of a clear migration error. app/core/schema_sync.py compares the database's Alembic revision against head at startup (development/test only). A clean behind-head database is upgraded automatically and logged. A database where a pending migration would create a table that already exists physically -- the create-all-without-stamp state -- refuses to start with the exact stamp-then-upgrade recovery instead of crashing into "table already exists". A brand-new empty database is stamped at head directly after create_all, since create_all already builds head's exact shape. Production/staging are unaffected.
…es (#166) Adds tests/test_schema_sync.py (fresh-db stamping, clean auto-upgrade, physical-drift refusal with actionable recovery, a regression test that the recommendation never silently skips a pending column-only migration, production/staging no-op, up-to-date no-op). The client fixture in conftest.py and the near-duplicate fixture bodies in test_ingestion_resource_budgets.py/test_rate_limit.py called create_all directly before the app's lifespan ran, leaving every table present but unstamped -- the new drift check misread that as physical drift on every test run. Fixed by having those fixtures stamp head the same way the lifespan does for a genuinely fresh database. Documents the behaviour and manual recovery commands in apps/backend/README.md.
…nd API (#162) The analysis worker persists many facts via frequent small commits during extraction while API request handlers poll concurrently. SQLite's default rollback-journal mode briefly locks readers out around each individual commit -- not for a transaction's whole open-but-uncommitted duration as might be assumed -- and under sustained concurrent commit/read pressure that adds up to real 'database is locked' errors. Every SQLite connection now gets PRAGMA journal_mode=WAL and a 5s PRAGMA busy_timeout (app/core/database.py, guarded to SQLite only, a no-op on PostgreSQL). WAL lets a reader always see the last committed snapshot independent of what the writer is doing, removing the collision entirely. Deliberately left the analysis worker's transaction boundaries unchanged: audited them and found them tightly coupled to the job-recovery guarantees (leases, retries, stale-worker takeover) documented in that module's own docstring. WAL removes the actual symptom without needing to touch that.
…tection fix(core): detect and resolve dev database schema drift at startup (Closes #166)
fix(core): eliminate SQLite lock contention between analysis worker and API (Closes #162)
docs: add Partha logo v1 and v2 SVG assets
…oyment grouping Documentation's Architecture section only listed flat module.role buckets with no notion of layering, and its Deployment classifier relied on `and` silently binding tighter than `or` to keep unrelated YAML out of the deployment list. Rebuild layering from sealed ri.v1 facts already available via SnapshotQueryService: modules now carry a heuristic layer (shared with ArchitectureAnalyzer via a single app.intelligence.classification helper, so Documentation and the Architecture page cannot disagree), and file-to-file RiEdge facts are aggregated into cross-layer relationship counts. Group the deployment predicate with explicit parentheses so the extension+token requirement can't be silently broken by a future added clause.
Recall was enforced at a looser 0.90 since Issue #94's provisional Phase-0 bar, while precision was already 0.95. Re-running the full 23-fixture golden corpus against the current real extractors (python-ast, typescript, dependency manifest) on this branch measures precision 1.0000 / recall 1.0000 with zero false positives/negatives, comfortably clearing a 0.95 bar on both metrics, so raise recall to match precision and pin both in a regression test guarding against a future silent threshold decrease.
…ark-hardening fix(documentation): layer architecture, fix yaml deployment grouping, raise benchmark recall to 0.95
…p tracked in #182 SYSTEM_OVERVIEW.md incorrectly stated symlink-escape rejection applied to the GitHub clone path. Only upload extraction guards symlinks (storage/local.py:121); the clone tree walk follows symlinks (issue #182, filesystem disclosure + recursion). Corrected the import diagram, validation node, and the untrusted-input note to scope the guard to upload and reference #182.
docs(system-overview): scope symlink-escape claim to upload; clone gap tracked in #182
The poll loop rebuilt its interval on every progress tick (repository object identity changed each response) and kept scheduling another request even after a completed/failed/cancelled response, leaving the "Analysing Repository" page stuck until a manual reload. The loop now stops itself on a terminal status and the effect only restarts on an actual status/error change, not every progress percentage.
Direct navigation to /dashboard or /analysis/{id}/architecture hit
react-router's default error boundary ("Unexpected Application Error!
404 Not Found") because no route matched them and there was no
catch-all. /dashboard now aliases the Dashboard surface at /, the
architecture deep link selects that repository and hands off to
/architecture, and any other unmatched path renders a NotFound page
instead of crashing. The auth guard still gates all of this.
…mport (#398) (#399) RepositoryParser now skips .DS_Store, __MACOSX/, and ._<name> AppleDouble sidecars when walking an extracted archive or cloned checkout -- both the upload and GitHub import paths call the same parser before anything downstream reads the tree, so one fix covers both. A sidecar shares its real counterpart's extension while being opaque binary, so left unfiltered it looks like a second source file to every downstream extension-based check. LocalStorage.extract_archive also physically strips these from the repository's own storage directory after extraction, so the artifacts that only ever come from a zip (not a git clone) don't linger on disk either.
README.md said repository lineage was future work and that RFC-0002 was 'design only: no table, column, or surface exists yet.' Both were wrong -- #299 implemented it: the repository_lineages table, owner-scoped grouping, sequence allocation, and duplicate-revision detection all exist and run on every GitHub import. What's actually still missing is a read API and UI, tracked separately as #400. Added a repository-lineage row to the capability registry (app/extraction/support_matrix.py) reflecting that same split -- partial support, data layer implemented, no read surface yet -- and regenerated README's generated capability table from it.
…ng (#403) (#404) Every route is code-split via lazy, so the first render always has at least one route whose module hasn't downloaded yet. With no HydrateFallback on any ancestor, react-router rendered nothing for that gap and logged 'No `HydrateFallback` element provided to render during initial hydration' on every route load. Wrapped the route tree under one pathless root route with a HydrateFallback (the same spinner RequireAuth already uses for its own session-initialising gap).
ruff check/format --check only ever ran against app scripts, so tests/ accumulated real lint issues nothing caught: a genuinely undefined name (tests/benchmark/runner.py referenced Fact without importing it -- silently harmless only because the file uses `from __future__ import annotations`, which stores the annotation as a string instead of evaluating it), a few unused imports, and two f-strings with no placeholders. Fixed all of them. tests/benchmark/fixtures is excluded: it holds deliberately malformed and adversarial Python source used as golden-benchmark fixture input (Issue #94), not our own code, so linting it is a category error. Reformatted every file under tests/ with ruff format to match -- a mechanical, one-time diff -- and updated ci.yml and DEVELOPMENT.md to run both checks against the same tests scope from here on. mypy stays scoped to app/scripts; that's a separate, larger effort tracked on its own.
GET /repositories/{id}/lineage returns the history of repository imports
the requested repository belongs to, most recent first -- the read surface
RFC-0002's implementation (#299) never got.
A standalone import (an upload, or a GitHub import whose ref never
resolved -- RFC §4.3/§6) has no lineage row to read: this returns
isLineaged: false and a one-entry history containing only the requested
repository, rather than fabricating a lineage or a 404. Every entry
reports its own sequence, revision, status, and uploadedAt, plus
isCurrent for whichever repository id was requested -- the same entry
carries isCurrent regardless of which member of the lineage you ask
through, so a client can render one shared timeline no matter which
import it started from.
Two new RepositoryRepository accessors (get_lineage_for_owner,
list_lineage_members) do the owner-scoped reads; everything stays scoped
through the existing RepositoryService/_get_record 404 semantics, so a
cross-owner or nonexistent repository id behaves exactly like every
other repository route.
Task 9 (the History UI) is a separate PR after this one, per repo
convention.
…literals (#408) (#409) requests.get(f"https://api.example.com/users/{user_id}") and fetch(`https://api.example.com/users/${userId}`) previously got no destination fact at all -- the extractor only accepted a plain string literal, so any interpolation anywhere in the URL fell back to a 'dynamic HTTP destination is unsupported' diagnostic, even when the origin itself never depended on the interpolated value. Both extractors now also try the literal text an f-string/template literal opens with, up to its first interpolation. That's only trusted once the authority is fully closed off within the literal text -- already containing the '/' that starts the path -- since otherwise the interpolation could still be extending the host itself (f"https://{tenant}.example.com"), or the more dangerous f"https://api.example.com{suffix}" where a suffix that doesn't start with '/' would silently become part of the hostname. Neither case extracts anything; both keep the existing diagnostic. app/extraction/http.py: refactored describe_destination's origin-parsing core into a shared _split_origin, added describe_destination_from_literal_prefix for the new case.
Adds a History tab to the repository detail page, backed by the read API from #407. Shows every import in the current repository's lineage, most recent first, with its sequence, status, revision, and upload time, and links to every other member. A standalone import (upload, or a GitHub import whose ref never resolved) gets a one-entry view with a short note explaining why -- not an empty state or a hidden tab. useRepositoryLineage follows the same fetch/status pattern as useInsights/useDependencies; repositoryService.getLineage and backendService.fetchRepositoryLineage wire it to the existing API-client layers.
…ry with 7 updates (#395) Bumps the frontend-minor-and-patch group with 7 updates in the /apps/frontend directory: | Package | From | To | | --- | --- | --- | | [@xyflow/react](https://github.com/xyflow/xyflow/tree/HEAD/packages/react) | `12.11.3` | `12.11.5` | | [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.18.2` | `7.18.3` | | [@testing-library/react](https://github.com/testing-library/react-testing-library) | `16.3.2` | `16.3.3` | | [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.1.0` | `6.1.1` | | [eslint](https://github.com/eslint/eslint) | `10.8.1` | `10.9.1` | | [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.5.4` | `0.5.5` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.67.0` | `8.69.0` | Updates `@xyflow/react` from 12.11.3 to 12.11.5 - [Release notes](https://github.com/xyflow/xyflow/releases) - [Changelog](https://github.com/xyflow/xyflow/blob/main/packages/react/CHANGELOG.md) - [Commits](https://github.com/xyflow/xyflow/commits/@xyflow/react@12.11.5/packages/react) Updates `react-router-dom` from 7.18.2 to 7.18.3 - [Release notes](https://github.com/remix-run/react-router/releases) - [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@7.18.3/packages/react-router-dom/CHANGELOG.md) - [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.18.3/packages/react-router-dom) Updates `@testing-library/react` from 16.3.2 to 16.3.3 - [Release notes](https://github.com/testing-library/react-testing-library/releases) - [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md) - [Commits](testing-library/react-testing-library@v16.3.2...v16.3.3) Updates `@vitejs/plugin-react` from 6.1.0 to 6.1.1 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.1/packages/plugin-react) Updates `eslint` from 10.8.1 to 10.9.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.8.1...v10.9.1) Updates `eslint-plugin-react-refresh` from 0.5.4 to 0.5.5 - [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases) - [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md) - [Commits](ArnaudBarre/eslint-plugin-react-refresh@v0.5.4...v0.5.5) Updates `typescript-eslint` from 8.67.0 to 8.69.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@xyflow/react" dependency-version: 12.11.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-minor-and-patch - dependency-name: react-router-dom dependency-version: 7.18.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: frontend-minor-and-patch - dependency-name: "@testing-library/react" dependency-version: 16.3.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-minor-and-patch - dependency-name: "@vitejs/plugin-react" dependency-version: 6.1.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-minor-and-patch - dependency-name: eslint dependency-version: 10.9.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-and-patch - dependency-name: eslint-plugin-react-refresh dependency-version: 0.5.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: frontend-minor-and-patch - dependency-name: typescript-eslint dependency-version: 8.69.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…uping (#396) (#397) - entrypoint role now maps to a genuine 'entrypoint' node type instead of being misclassified as 'frontend' regardless of language - package.json/pyproject.toml/lockfiles no longer get grouped into an architecture module of their own -- they already surface as dependency evidence, and the manifest basename filter now strips the 'file:' prefix before matching so it also catches root-level manifests, not just nested ones - a bare top-level file's module display name is no longer Title Cased, which was corrupting real filenames like app.py into App.Py
* refactor(scale): claim analysis jobs through an explicit control plane Analysis jobs were already durable, leased, cancellable and recoverable, but "who owns a job" was expressible only as private methods of AnalysisWorker: the claim compare-and-swap, the lease-renewal compare-and-swap, the expired-lease scan and the ownership predicate all lived inside the executor. A second worker process could not participate in the queue without importing the class that runs the extraction pipeline. Introduce app/workers/control_plane.py as that boundary. AnalysisControlPlane is the protocol; DatabaseAnalysisControlPlane is the v1 implementation, and it is the existing analysis_jobs table rather than a new broker — the durable row is already the authority for identity, attempt budget, cancellation and lease expiry, so a queue service would only add a second, weaker source of truth. JobLease is a value, not a handle: ownership is re-proved by a guard on every mutation, so a stale lease can never write to a reclaimed job. LeaseRenewal distinguishes renewed / lost / deferred, making the previously unnamed SQLite single-writer case explicit instead of an inline exception filter. AnalysisWorker keeps execution only: running a job it already owns through the ri.v1 pipeline and deciding its terminal transition. Every statement moved across is unchanged, so the claim/lease/cancellation contract is identical; _claim stays as a row-shaped delegation for existing callers. Refs #324, #210 * refactor(scale): move the analysis worker loop out of the API app app.main did not merely host a worker, it *was* the control loop: it minted the ownership token, built the worker, owned the poll cadence, decided when to sweep stale leases, and joined the thread on shutdown. Nothing outside a FastAPI lifespan could run a worker without copying that policy, which is the tight coupling #324 exists to remove. AnalysisWorkerRunner now owns it. run_forever is a plain blocking call, so the loop a standalone worker process needs already exists here rather than being locked inside an async context manager; threading is an implementation detail of this runner, not of the boundary. app.main is reduced to start/stop, and the in-process single-worker path is preserved exactly as #324 requires during migration. Building the standalone deployment stays with #210. new_worker_id moves alongside it, since worker identity is what every control-plane ownership guard compares; its test moves with it and now asserts uniqueness across many tokens rather than two. Refs #324, #210 * test(scale): cover queue ownership, leases and cancellation 31 cases over the boundary itself, not the extraction pipeline: claiming and eligibility (including that next_attempt_at backoff hides a job from the queue), the duplicate-claim race, separate-job claiming, expired-lease reclaim, active- lease protection, ownership enforcement on both mutation and renewal, the lost-lease handoff signal, cancellation visibility across a reclaim, the cancel-not-requested guard that keeps cancellation idempotent, and the runner's drain/sweep/failure-isolation/shutdown behaviour. Every race is expressed as an explicit interleaving or a threading.Barrier; nothing sleeps waiting for a race, so a slow machine cannot turn a correctness assertion into a flake. The duplicate-claim case pins the candidate each worker read so both genuinely reach the compare-and-swap — verified by mutation: deleting the status='queued' guard fails it, and an earlier version of the test that let the loser re-read the queue did not. next_eligible_job_id is split out of claim for that reason: the candidate read is the half of a claim that carries no ownership, and naming it makes the race window visible to a reader as well as reachable by a test. The threaded race is gated on PARTHA_TEST_PG_URL, following the repository's established pattern — SQLite serialises writers, so only a real MVCC server exercises two claims genuinely in flight. Refs #324, #210 * docs(scale): document the analysis control-plane boundary Record what now runs: the control plane and runner as separate component rows, the claim/lease exchange in the ingestion sequence, and the fact that the API process hosts a worker without owning the queue. The known-limits entry is widened rather than softened — analysis is still whole-repository and still runs one in-process worker per API process. No standalone worker deployment exists, and this documents current behaviour only. Refs #324, #210 * chore(scale): reformat for ruff's now-widened tests/ scope (#405) One line in this file didn't match the formatter after tests/ came under ruff format --check in #405, which landed after this branch opened. Mechanical only, no logic change. --------- Co-authored-by: PARTH J ROHIT <parthrohit60@gmail.com>
…indings (#412) (#413) A real analysis of a Django project surfaced 608 medium-severity Unresolved relationship findings, almost entirely 'imports has no resolvable target' on boilerplate config files (asgi.py/wsgi.py/settings.py/urls.py) whose imports are mostly import os, from django.core.wsgi import get_wsgi_application, from pathlib import Path -- a standard-library or correctly-declared third-party import, not a gap. Same root-cause pattern #392 fixed for builtin calls, on the import side. New app/review/import_dispositions.py decides, purely at review time, whether an unresolved import-kind diagnostic should be promoted to a finding: not when its target is the Python standard library or a Node builtin module, and not when it matches a package the repository's own manifest declares (checked against the sealed snapshot's real dependency nodes, using the same package-root extraction and pypi/npm identity the resolver itself uses -- pulled out of intelligence/resolution.py into a shared extraction/naming.py function so the two can never independently drift on what 'the package' means for a given import). The resolver's own output is untouched -- same diagnostics, same facts, same ri.v1 data. Only whether an import diagnostic is surfaced as a finding changes. A relative import, or a bare specifier that matches neither stdlib/builtin nor a declared dependency, still stays a finding: the genuine case (a typo, a missing dependency declaration, a real extractor gap). route_handler/injects/calls_service diagnostics are untouched. Verified against the real Django-boilerplate pattern (12 raw diagnostics, unchanged; 12 findings before this fix, 5 after -- the 7 suppressed are exactly the stdlib imports; the 5 remaining are calls-kind diagnostics, issue #393's separate, distinct territory, not touched here).
) Ollama runs inference on the same machine PARTHA runs on, and PARTHA had no concurrency control anywhere in its provider call path -- every request that reached the app was forwarded to Ollama immediately and independently. Measured directly: Ollama's own backend already serializes actual token generation to one request at a time by default, so extra concurrency here never got anything done faster -- it only meant more requests held open at once (more peak memory) and the elevated CPU/memory load running as one long unbroken stretch instead of several shorter ones. A module-level anyio.Semaphore(1) in OllamaProvider.complete() caps in-flight requests to Ollama at 1. A request beyond that queues and waits -- it never fails or gets rejected; this is resource management, not a rate limit. Scoped to Ollama specifically: the four hosted providers (OpenAI, Anthropic, Gemini, OpenRouter) share the same post() transport but each call a fixed remote URL with their own server-side infrastructure, so a concurrent request to one of them doesn't compete with the user's own machine the way local inference does -- they stay unlimited. anyio.Semaphore rather than asyncio.Semaphore: the module-level object is shared process-wide, and asyncio.Semaphore permanently binds itself to whichever event loop first contends it -- fine for the one persistent loop a real server runs on, but it broke exactly that way the first time two of my own new tests each drove real concurrent contention through a separate asyncio.run(). anyio's version (already used elsewhere in this provider layer) doesn't have that failure mode. Verified for real: reproduced the same 3-concurrent-request burst against a real Ollama instance through PARTHA's own /ai/query endpoint. Before: llama-server held ~190-200% CPU (near 2 full cores) continuously for the whole ~34s burst, growing from 16GB to 17GB resident. After, the same burst: ~95-97% CPU (~1 core), memory flat around 700MB, completions at 9.5s/17.7s/27.2s -- a clean queue, the first request finishing in exactly its own solo time rather than being slowed by the other two.
…lama timeout, provider doc (#421) Squash of 5 commits: - fix(marketing): replace the slide-in drawer with a centered modal dialog - fix(backend): stop Ollama requests being cut off mid-generation (per-request timeout; tight connect, long read budget for local inference) - fix(backend): split Repository Insights unresolved-relationship count into genuine in-repo gaps vs. references into external/platform code (read-time only; no stored fact changes) - docs: add docs/operations/AI_PROVIDER_SETUP.md, an end-to-end guide for connecting an AI provider - chore(deps): bump js-yaml override to 4.3.2 for GHSA-2883-xcg3-v3hh All CI checks green. Merged with admin bypass at the repository owner's direction. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ences (#423) PR #421 fixed the Repository Insights "Unresolved relationships" number. The Architecture page has the same problem from the same cause: any RI-RES-UNRESOLVED diagnostic marked its containing module "unresolved" (red badge) and was listed in full under Diagnostics -- so a module whose only unresolved references are calls into `fs`, `flask`, stdlib, or a language builtin showed up as broken, and the diagnostics list ran to thousands of entries. Reuse the same classifier. The shared helper is generalised: the per-diagnostic DB context load and the external-vs-in-repo test now live in `app/insights/relationship_diagnostics.py` as `load_unresolved_relationship_context` + `is_external_unresolved`, and both `RepositoryInsightsBuilder` and `ArchitectureAnalyzer` call them. `ArchitectureAnalyzer._edges_for_modules` now drops external-reference RI-RES-UNRESOLVED diagnostics before it marks module relationship state *and* before it builds the returned diagnostics list. Genuine in-repo gaps and every RI-RES-AMBIGUOUS are untouched; the raw resolver diagnostics stay available via the intelligence evidence API. Measured on real snapshots: files that would flag a module drop 200->41 and 87->18; the architecture diagnostics list drops 2659->228 and 676->95. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Six dead "coming soon" footer toasts (Privacy, ri.v1 spec, Language matrix, Changelog, About, Legal) and the generic LinkedIn/X links now all resolve to real content: README sections, the ri.v1 RFC, REPOSITORY_INTELLIGENCE.md, GitHub Releases, LICENSE, linkedin.com/in/parthrohit, and Discord for X (no X account yet). The unavailable-notice toast + state are removed. apps/marketing typecheck/lint/build green; CI 11/11 green.
) Documentation-follows-behaviour catch-up for this session's backend changes (PR #421, PR #423): - REPOSITORY_INTELLIGENCE_RESOLUTION.md: new "How read consumers present unresolved warnings" subsection -- sealed RI-RES-UNRESOLVED warnings are never edited, but Insights and Architecture classify a reference into a declared dependency / the language platform as expected rather than a gap. Names the shared classifier and the raw-total escape hatch. - REPOSITORY_INTELLIGENCE.md consumers table: Architecture and Repository insights rows note the same split. - apps/backend/README.md: Ollama's longer read budget vs. hosted 60s, the 10s connect timeout, and the one-in-flight cap; link to the new AI provider setup guide from the egress section. - README.md: link docs/operations/AI_PROVIDER_SETUP.md from the documentation index.
The landing page was a single 1728px-wide authored design image with invisible tap targets overlaid at percentage coordinates. On a phone it only shrank, so every bit of text became unreadable; tablets were a squashed desktop layout. Add a breakpoint split at 1024px: - >= 1024px: the authored canvas + hotspots, unchanged. - < 1024px: MobileLanding -- a real responsive layout (sticky header, hero, "how it works" steps, capabilities grid, a native <details> FAQ accordion, CTA band, stacked footer) built from the same content and wired to the same DemoModal / RunItYourselfModal dialogs. Only one layout is ever in the DOM (useMediaQuery via useSyncExternalStore), so the 3.3 MB design SVG never downloads on mobile. Footer links, FAQ copy, and site URLs are now shared data modules (src/data/site.ts, src/data/faq.ts) so the two layouts can't drift; the desktop overlay's positioned footer list is derived from the same FOOTER_COLUMNS. The mobile footer's fourth column is correctly headed "Connect" (the artwork still reads "COMPANY" twice -- that text is outlined vector paths in the SVG and needs the asset regenerated). Verified on apps/marketing: typecheck, lint, build. Manual: 375 / 768 / 1280px in light and dark -- no horizontal overflow, hero/steps/cards/FAQ readable, accordion expands, anchor links scroll under the sticky header, theme toggle works in both layouts, desktop canvas unchanged at >= 1024, layout switches on a real resize event.
…landing feat(marketing): responsive landing page for phones and tablets
The < 1024px layout from #426 was readable but generic. Rework it to carry the authored design canvas's visual language, since there is still no authored mobile comp to match pixel-for-pixel: - Heavy Title Case display headings (Inter 800), the design's own hero line, and an editorial serif italic (Newsreader) for the hero sub-copy. - The "Meet Partha" script flourish (Caveat) and the outline eyebrow pill with an orange dot. - Pill CTAs with icons: outline + a filled play triangle, solid orange + an external-link glyph. - Three alternating peach / teal-blue tinted story cards ("See How The System Fits Together", "Know Where Every Finding Came From", "Honest About Its Limits"), matching the desktop card treatment. - How-it-works, capabilities, FAQ, and the CTA band restyled to the same system (warm borders, tinted number / icon chips, + toggles). Adds --brand-blue (the design's #006298 teal) as a token with a dark-mode value, a dark-mode --secondary, and font-serif / font-script families; extends the Google Fonts link with Inter 800, Newsreader italic, Caveat. Desktop (>= 1024px) is untouched. Verified on apps/marketing: typecheck, lint, build. Manual at 390 / 844 and 1300px, light and dark: fonts load (no fallback), no horizontal overflow, every section renders, desktop canvas unchanged.
…tions v1
Rework against the PARTHA Foundations v1 handoff (Typography and Color)
rather than colours and fonts eyeballed off the design SVG:
Type — the three assigned families, real weights loaded (the handoff
treats fallback rendering as a failure):
- font-display = Montserrat Alternates (500/600/700): headings, buttons,
short feature titles, nav.
- font-sans = Proza Libre (400/500, +italic): all reading text,
metadata, labels. Replaces Inter as the default body family.
- font-accent = Cormorant Upright: exactly one expressive phrase
("Partha" in "Meet Partha"). Newsreader / Caveat removed.
Colour — tokens set to the approved hex values in their roles:
- --primary Signal Orange #FA4D01, CTA / focus only, kept scarce.
- --secondary Deep Blue #006298, architecture / technical.
- --burnt-orange #AA3D00 (new token), selective secondary emphasis.
- --foreground Deep Plum #392135, headlines and body text.
- dark mode uses Deep Plum as the dark surface, not pure black; the
pre-hydration flash guard matches.
MobileLanding restyled to the type scale and the ~70% neutral / 15% plum /
10% blue / 5% orange balance: subtle tinted story cards, blue technical
labels, Signal Orange confined to the one primary CTA, the eyebrow dot,
and the accent word. Shared dialog headings pick up font-display.
Desktop (>= 1024px, the authored canvas) is unaffected. Verified on
apps/marketing: typecheck, lint, build; 390 / 844 and 1300px, light and
dark -- all three fonts load (no fallback), no horizontal overflow.
…d-match feat(marketing): responsive mobile landing, aligned to Foundations v1
… layout
The mobile layout was type + colour + icon chips. Compose the designer's
own exported SVG assets (Partha Assets/) so it carries the same
illustrations as the authored desktop canvas:
- hero: the soft radial-glow + wireframe visual behind the headline, and
the asterisk accent mark in the eyebrow pill.
- product story cards: each now shows its real graphic -- the resolved
service graph ("see how the system fits together"), the
auth.service.ts evidence trace ("know where every finding came from"),
and the extracted / partial / not-assessed summary ("honest about its
limits").
- how it works: each step shows its own designed illustration (the
revision -> repository -> sealed snapshot loop; the
files/symbols/dependencies/relationships extraction; the finding traced
to evidence).
- CTA: the plus-blob mark as a faint corner decoration.
Assets are copied into src/assets/landing/ (self-contained build),
rendered with loading="lazy", ~230 KB gzipped total and only on the
< 1024px path -- the 3.3 MB desktop canvas still never loads on a phone.
Capabilities collapse to a plain list; the hero decoration is clipped so
there is no horizontal overflow.
Foundations v1 type and colour roles are unchanged. Desktop (>= 1024px)
is unaffected. Verified on apps/marketing: typecheck, lint, build; 390 /
844 and 1280px, light and dark -- fonts load, no overflow, all
illustrations render, desktop canvas unchanged.
…gner-assets feat(marketing): use the designer's illustration assets in the mobile landing
… ci: harden browser-acceptance apt step (#429) QA-pass findings #1 (lineage read API + UI exist — docs said they didn't; support_matrix.py + regenerated README block + README prose + backend README endpoint row) and #2 (SameSite=Lax refresh cookie → SPA+API must be same-site; SYSTEM_OVERVIEW.md + DEVELOPMENT.md). Plus a CI fix: strip the flaky dl.google.com Chrome apt source before `playwright install --with-deps` so a Google-side index hash-mismatch can't red the browser-acceptance job. Docs + CI only, no product behaviour change. All checks green.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
parthrohit22
added a commit
that referenced
this pull request
Sep 9, 2026
chore: sync dev with main after the #430 promotion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promote
devtomain.mainhas been frozen at commit #38 (9 Jul 2026)while the entire product was built on
dev; this bringsmaincurrent.mainis a direct ancestor ofdev→ clean fast-forward, no conflicts.ri.v1snapshot pipeline, Architecture / Dependencies / Engineering Review /Insights / Documentation / Export surfaces, Repository Intelligence query API,
repository lineage, AI provider integration + workspace, invite-gated auth +
OAuth, the durable analysis worker, and the standalone marketing site.
devCI is green ona41180a(this HEAD).Pre-merge end-to-end QA (this session)
Ran against a live stack (Postgres 15
partha_dev, Redis, Ollama).pytest(PG + Redis)vitest0016_approved_emails; migrate-from-empty validated by CI's isolated-Postgres rehearsal jobTwo QA findings were fixed on
devbefore this PR (#429): lineage docscorrected to match the shipped read API + UI, and the
SameSite=Laxrefreshcookie's same-site deployment requirement documented.
Deployment notes for whoever cuts over
mainmainjumps 444 commits at once. The Vercelmarketing project is wired to this repo; confirm which branch its
Production deployment tracks before/after merging.
development/testwithout realAUTH_SECRET_KEY(≥32), a valid FernetAI_ENCRYPTION_KEY, explicitCORS_ORIGINS, and — for a local/custom Ollama endpoint — the egressallowlists. This is by design.
SameSite=Laxrefresh cookie) —see
docs/architecture/SYSTEM_OVERVIEW.md§ Authentication and session flow.maincurrently carries committeddist/artifacts; this merge removes them(
devis hygiene-clean).v0.x.0tag after the merge triggersrelease.yml(validation + GitHubRelease).
Testing performed
devCI green ona41180a; full local suite + live E2E as tabled above.Security / data considerations
No new code in this PR — it is a branch promotion. All controls
(owner-scoping, invite gate, encrypted provider keys, egress allowlist, DNS
pinning) are unchanged from
dev.