> ## Documentation Index
> Fetch the complete documentation index at: https://arclux-os.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# decisions

> Detail progres: decisions

# ARCLUX Progress — Design Decisions

Why things were built the way they were. See PROGRES.md for the index.

## 2026-08-03 — Update — GitHub infra + features/graph decision

**Repo infrastructure added**: branch ruleset on `main` (PR required, no
direct push — verified by testing it against ourselves), PR + issue
templates (`.github/`), 10 GitHub Issues created from open items in this
file, release tag `v0.1.0-alpha` published, `CONTRIBUTING.md` rewritten
(was stale: said pnpm/turbo, said detectors don't exist — now says npm,
18/18 detectors, references playground/ testing pattern).

Also removed `turbo.json` (0 bytes, unused leftover — project uses npm
directly, `turbo` command never actually run against this repo).

**apps/web/features/graph/* decision*\*: `useGraph.ts` implemented as a
thin re-export of `GraphProvider.tsx`'s `useGraphContext()`. The other 4
files (`graphStore.ts`, `graphEvents.ts`, `useGraphLayout.ts`,
`useGraphSelection.ts`) are DELIBERATELY left as documentation-only stubs
— `GraphProvider.tsx` already owns all graph state (transform, positions,
dimensions, selection) via React Context. Do NOT implement a separate
store/hooks layer here; it would create two sources of truth for the same
state. Same class of risk as the `packages/ui/graphColor.ts` /
`theme/graphColors.ts` naming collision noted earlier.

## 2026-08-03 — Update — parseTsx.ts and parseTsConfig.ts confirmed intentionally empty

Verified, not just assumed: `packages/parser/typescript/parseTsx.ts` and
`parseTsConfig.ts` will stay empty stubs permanently, not because they're
"not done yet" but because their functionality already lives elsewhere:

* `.tsx` parsing: handled inside `parseTs.ts` itself via
  `ts.ScriptKind.TSX` (checked its `extensions` field and ScriptKind
  selection logic directly).
* tsconfig.json parsing: handled inside
  `packages/graph/resolveAliases.ts` (moved from packages/indexer/ on
  2026-08-13, fix for issue #2 circular dependency indexer\<->graph),
  which reads tsconfig.json /
  jsconfig.json directly (with comment/trailing-comma stripping) for
  path-alias resolution.

Both files now have a comment explaining this, so a future session
doesn't attempt to implement duplicate logic in either of them — same
class of risk previously flagged for `packages/ui/graphColor.ts` vs
`theme/graphColors.ts` (that one is still an open risk; these two are now
resolved/documented).

## 2026-08-04 — Update -- PLANNED (not yet built): graph node visual impact indicator

> **\[STATUS UPDATE, 2026-08-07]: this plan is now implemented.** See the
> "UPDATE: Graph impact halo — implemented" entry near the bottom of this
> file for what actually got built and what's still open (tier thresholds
> not yet tuned, not yet visually verified). The plan below is kept as-is
> for historical context -- don't re-implement it.

**Goal**: in the dependency graph view, high-impact nodes (files with
many consumers, e.g. logService.ts in a VS Code-scale repo with 430
importers) should be visually distinguishable WITHOUT clicking each node
first. Motivated by dogfooding: currently every file node is the same
blue regardless of impact, so finding "the important files" requires
clicking through hundreds of nodes one at a time.

**Explicit non-goal, confirmed with user**: do NOT change node color by
type. graphNodeColors (theme/graphColors.ts) currently colors nodes by
GraphNodeType (file=blue, external-package=amber, route=purple,
component=teal, hook=red) -- that must stay as-is, it's how users tell
node kinds apart. Impact must be a SEPARATE visual signal layered on top
(halo ring and/or radius size), not a replacement for type color.

**Data availability, already confirmed**:

* packages/shared/types.ts's GraphNode interface has NO importedBy/fan-in
  field built in.
* BUT DependencyGraph.edges (GraphEdge\[], with source/target as GraphNode
  ids) is already sent to the browser in full via GET /api/graph
  (packages/graph/serializeGraph.ts passes graph.edges through
  unmodified, no changes needed there).
* Therefore: fan-in count per node can be computed CLIENT-SIDE by
  counting how many times each node id appears as an edge's `target`
  across graph.edges. No backend/API changes needed at all -- this is a
  frontend-only feature.

**Implementation plan, in order**:

1. In `apps/web/components/graph/GraphProvider.tsx`: add a `useMemo`
   that, whenever `graph` changes, builds `Map<nodeId, number>` by
   iterating `graph.edges` and counting occurrences of each `edge.target`.
   Expose this as a new `importCounts: Map<string, number>` field on
   `GraphContextValue` (add to both the interface and the `value` object
   near the bottom of the file, same pattern as the existing `positions`
   field).

2. In `apps/web/components/graph/GraphCanvas.tsx`: at the `<GraphNode>`
   render call around line 308-315 (confirmed exact location -- inside
   `{graph.nodes.map((node) => { ... return <GraphNode ... /> })}`,
   sibling props to `isSelected`/`isHovered`), pull `importCounts` from
   `useGraphContext()` (already imported/used elsewhere in this file
   presumably -- verify) and pass
   `importCount={importCounts.get(node.id) ?? 0}` as a new prop.

3. In `apps/web/components/graph/GraphNode.tsx`: add `importCount: number`
   to `GraphNodeProps`. Define tier thresholds (user's suggested starting
   point: High >100, Medium 20-100, Low/normal \<20 -- these are
   arbitrary and should be tuned after seeing it rendered against a few
   real repos of different sizes, e.g. python-demo vs vscode vs next.js,
   since "100 importers" means very different things in a 50-file repo
   vs a 15,000-file repo). For High/Medium tiers, render an additional
   `<circle>` halo BEHIND the existing node circle (larger radius, no
   fill, a neutral stroke color like white or amber at low opacity --
   NOT reusing graphNodeColors, since that would collide with the
   type-color meaning). Consider also scaling BASE\_RADIUS slightly for
   High-tier nodes. Existing isSelected halo logic
   (`{isSelected && <circle r={radius + 5} ... />}`) is a useful
   reference for the halo-circle pattern already used in this file --
   don't duplicate logic, structure the new halo consistently with it.

4. Test with `enableMouseInteraction`-style verification: run against
   playground/python-demo first (small, fast iteration) to confirm no
   crash/visual regression, THEN test against a large real repo (the
   user has already tested vscode, react, vercel/next.js, microsoft/vscode
   via the /new flow against localhost -- reuse one of those) to confirm
   the tiering actually looks meaningful at scale, not just correct in
   theory. Screenshot verification in-browser required before considering
   this done -- typecheck alone is not sufficient evidence per this
   project's established verification standard.

5. Consider whether label text position (`x={radius + 6}` in
   GraphNode.tsx) needs to account for the halo radius too, or if it's
   fine referencing only the inner circle's radius -- check visually.

6. Consider whether d3-force's collision detection (GraphCanvas.tsx,
   look for wherever simulation nodes get a radius/collision force) needs
   updating so bigger high-impact nodes don't visually overlap
   neighboring nodes now that some nodes are bigger than others -- this
   wasn't investigated yet, flagged as a real risk worth checking, not
   confirmed either way.

**Status**: planning/investigation only, ZERO code written yet. All file
line numbers and current-state details above were confirmed by directly
reading the files in this session -- safe to trust and start straight
from step 1 above without re-investigating from scratch.

## 2026-08-05 — Decision — same-package/same-namespace resolution: ONE generic pass, not per-language fixes

**Context**: Go graph (Kubernetes test) and Java graph (java-demo fixture)
both showed near-zero edges despite files clearly being related. Root
cause confirmed identical in both `parseGo.ts` and `parseJava.ts`'s own
comments (already documented by whoever wrote them, not discovered fresh
here): both Go and Java let files in the same package/directory reference
each other with ZERO import statements. `playground/go-demo`'s
cyclic\_a.go/cyclic\_b.go and `playground/java-demo`'s Main.java calling
Service/Models/Utils are both confirmed real examples of this. The parser
only extracts what's literally written, so these relationships never
reach `resolvePath.ts` as anything to resolve — there's no import
statement token to feed it in the first place.

**Decision**: do NOT write a Java-specific fix and a separate Go-specific
fix. This is one general problem — "files that share an implicit scope
need a same-scope dependency pass independent of import statements" —
that will likely also apply to C# (`namespace`) and Rust (`mod`) once
those parsers go further than manifest-only (`parseCsproj.ts`,
`parseCargoToml.ts` exist; `parseCSharp.ts`/`parseRust.ts` are still
empty). Build ONE resolution pass parameterized by "what counts as a
shared scope" per language (directory for Go, package declaration for
Java), not four copies of similar logic.

**Not yet built** — this is a design decision recorded for whoever picks
this up next, not an implementation. Same class of gap as
`resolveRoutes.ts` being empty (noted in parseGo.ts's own comment as a
parallel case).

**Referenced but not portable**: cloned `javaparser/javaparser` to
`~/research/javaparser` for its `SymbolSolver`/`TypeSolver` concepts —
it's a JVM library, not directly adaptable to TypeScript, but worth
reading for how a mature tool structures scope resolution before
designing ARCLUX's own pass.

## 2026-08-06 — Decision — issues assigned to a collaborator must also be marked in-file

**Context**: packages/parser/php/parsePhp.ts and
packages/parser/php/parsePhpRoutes.ts sit right next to each other.
parsePhpRoutes.ts is assigned to Alitindrawan24 via issue #53.
Someone Browse-ing packages/parser/php/ without first checking the
GitHub issues list has no way to know parsePhpRoutes.ts is spoken for

* it just looks like another empty file waiting to be filled in,
  identical in appearance to a genuinely unclaimed stub.

**Decision**: whenever an issue is filed that assigns a SPECIFIC file
or narrowly-scoped task to a collaborator, also add a short comment in
that file (or, if the file doesn't exist yet, in the most relevant
existing sibling file) stating the issue number and assignee. Do not
rely on the GitHub issue tracker alone to communicate this - anyone
working directly in the codebase (a session reading files, not
Browse-ing issues first) needs the signal to be visible at the file
level too.

Minimum content for the marker comment: issue number, assignee
username, one line on what's being built there. See
packages/parser/php/parsePhp.ts's comment (referencing issue #53 /
Alitindrawan24 / parsePhpRoutes.ts) as the template to follow.

This does not replace filing the GitHub issue - both are required, the
file comment is an addition for discoverability, not a substitute.

## Still empty, priority order for next session

1. `packages/indexer/resolveRoutes.ts` — unblocks entry-file-awareness for detectUnusedExports/detectOrphanFiles false positives
2. `packages/indexer/resolveExports.ts`, `resolveComponents.ts`, `resolveHooks.ts`, `resolveProviders.ts` — same family as resolveRoutes
3. `apps/web/components/explorer/Explorer.tsx`, `DependencyList.tsx` — FileDetails.tsx already exists but isn't wired to anything, this is why
4. `apps/web/lib/api.ts`, `graph.ts` — client fetch helpers, currently pages call fetch() inline
5. `packages/db/*` — persistence layer, 0%, needed before any "history over time" feature
6. `packages/indexer/updateIndex.ts`, `watchIndex.ts`, `indexSchema.ts` — incremental indexing, depends on packages/incremental being wired in first (not yet done)

## 2026-08-07 — DependencyList.tsx type confirmed against real API

DependencyList.tsx previously had a local GraphResponse/GraphNodeResponse/GraphEdgeResponse type, written before app/api/graph/route.ts's actual response shape was checked (its own comment admitted this). Verified: DependencyGraph.nodes/edges from packages/shared/types match exactly. Replaced the local guessed type with the real shared type. Going forward: don't guess API response shapes in component files -- check the actual route.ts handler first, even if it means a short delay before writing the component.

## 2026-08-07 — Next steps priority for future sessions

> **\[STATUS UPDATE, 2026-08-07 later same day]: item (1) below is now
> implemented** -- see "UPDATE: Graph impact halo — implemented" near
> the end of this file. Item (2) is still open.

Two concrete next steps identified this session, in suggested order: (1) Graph node visual impact indicator (halo ring for high-fan-in nodes) -- full implementation plan already documented in an earlier entry in this file (client-side importCounts via useMemo in GraphProvider.tsx, passed as importCount prop through GraphCanvas.tsx to GraphNode.tsx, rendered as an extra halo circle). Zero code written yet, ready to start from step 1. (2) Remaining inline fetch() calls that duplicate the pattern lib/api.ts's fetchJson() now centralizes -- ImpactSummary.tsx and GlobalSearch.tsx were the two examples that motivated building fetchJson() in the first place but were NOT themselves refactored to use it. Worth a follow-up pass to actually consume the helper there, plus check FileDetails.tsx and app/api/file/route.ts for the same duplicated pattern.

## 2026-08-07 — Graph impact halo: zoom-gated to avoid clutter

Resolved an open question from the original halo-ring plan: halos only render when zoom level is past a threshold (not always-on), avoiding visual clutter/overlap when zoomed out on dense graphs. Rejected alternatives: always-on halo (overlaps neighboring nodes in dense graphs), thicker border/stroke instead of halo (loses the 'grows with importance' visual cue that a halo radius gives). Implementation-wise this means GraphNode.tsx's halo render needs access to the current transform.scale (already available via useGraphContext()) and a MIN\_ZOOM\_FOR\_HALO constant to gate on.

## 2026-08-07 — UPDATE: Graph impact halo — implemented

The halo-ring plan described in the 2026-08-0X entry above (and listed as a next step in 'Next steps priority for future sessions') is now implemented: GraphNode.tsx renders an impact halo circle gated by zoomScale >= MIN\_ZOOM\_FOR\_HALO, importCount computed via useMemo in GraphProvider.tsx from graph.edges, passed through GraphCanvas.tsx. Tier thresholds (High >100, Medium 20-100) are still unverified against real repos -- that part of the original plan remains open. Not yet visually verified in-browser as of this entry. If you're reading the older halo entries above, they're outdated -- this is the current status.

## 2026-08-07 — Simulated stage progress instead of real backend streaming

`apps/web/components/graph/AnalyzingProgress.tsx` (new) replaces the
static "Analyzing repository…" text in GraphCanvas.tsx's `isLoading`
branch. Cycles through 5 stage labels (Cloning/Scanning/Parsing/
Resolving/Building) on a fixed timer + shows an indeterminate progress
bar, with a "taking longer than usual" message after \~17s.

**Chose client-only simulated progress over backend SSE streaming**:
the correct fix (backend emits real stage events, e.g. via
Server-Sent Events on /api/graph) would need analyzeRepository()'s
pipeline to become event-emitting instead of a single synchronous
return — a much larger change. This is a stopgap: the labels/timing are
tuned by feel, NOT derived from real pipeline telemetry. Large repos will
sit on the last stage indefinitely since there's no real signal to
advance further. Documented as such in the component's own comment so
nobody later mistakes this for actual progress reporting.

Addresses the UX gap noted in status-backlog.md's large-repo dogfooding
entry (microsoft/TypeScript's "Indexing failed" was indistinguishable
from vercel/next.js's "just slow" until it errored, because there was no
feedback at all).

Verified: `tsc --noEmit -p apps/web/tsconfig.json` clean. NOT yet
visually verified in-browser — pushed near a chat context limit.

**Coordination note**: `git pull` before this session started fast-forwarded
in unrelated changes to GraphCanvas.tsx/GraphNode.tsx/GraphProvider.tsx
from another session (fan-in halo indicator feature, matches an earlier
decisions.md plan) — merged cleanly, no conflict with this change since
they touch different parts of the same files.

## 2026-08-07 — QUICKSTART.md language kept English

QUICKSTART.md initially drafted in Indonesian during a mobile terminal session, per user preference for chat interaction. Decided to keep it English-only to match PROGRES.md and TOOLING.md conventions (all repo docs are English, Indonesian is only used in Claude chat sessions). No translation needed yet since the file is still short (3 sections: workflow, progress logging, pre-check for empty files).

## 2026-08-07 — README Contributors section intentionally removed

The Contributors section (contrib.rocks avatar grid) was intentionally removed from README.md by the user via a direct GitHub browser edit. A later session mistook this for accidental damage (based on the generic 'Update README.md' commit message, which matched the pattern of other stray browser-edit branches like GSF-001-patch-1/-2) and restored it -- this was wrong and got reverted. If README.md is missing a Contributors section in the future, that's the current intended state, not a bug to fix.

## 2026-08-07 — Next up: graph LOD (level-of-detail) rendering

> **\[STATUS UPDATE, 2026-08-08]: this plan is now implemented.** See "UPDATE: Graph LOD rendering — implemented" below.

**Status:** In Progress

Discussed but not yet started: extend the zoomScale-gating pattern already used for the impact halo (GraphNode.tsx, MIN\_ZOOM\_FOR\_HALO) to also gate label/icon visibility at low zoom, reducing DOM/render load when zoomed out on large graphs. Rough plan discussed: very low zoom (\<0.5) hides icon+label entirely (node becomes a plain dot), mid zoom (0.5-1) keeps current behavior (label only on select/hover), high zoom (>=1) optionally always shows labels for high-importance nodes. Checked reactflow-ref's Stress example for a reference pattern first -- it's a performance benchmarking harness, not an LOD implementation (React Flow handles this internally, not via example code), so no direct pattern to borrow. This is a natural extension of existing code (GraphNode.tsx's opacity=\{isSelected || isHovered ? ...} pattern at the label render, \~line 104), not a new subsystem. Prioritized as step 1 of 3 general performance directions discussed (graph viewer LOD/canvas rendering, pipeline parallelization+caching, new analysis features) -- graph viewer was picked first since it's the most immediately felt by users and doesn't overlap with collaborator-assigned work (call graph is assigned to xcontcom via issue #50).

## 2026-08-07 — Cache package design research (packages/cache)

**Status:** Not Started

Not Started

## 2026-08-07 — Cache package design research (packages/cache)

**Status:** In Progress

Researched before implementing packages/cache (CacheProvider.ts, fileCache.ts, graphCache.ts, memoryCache.ts, repositoryCache.ts -- all still 8-line stubs, zero consumers currently call any of them, no prior plan existed in progres/ for this). Checked packages/README.md and pipeline.ts/indexer -- confirmed nothing references cache yet, this is greenfield design work.

Studied \~/dependency-cruiser/src/cache/ as reference (NOT for copying, for architecture ideas -- user explicitly wants ARCLUX's cache to be MORE capable than this reference, not simplified). Key findings:

* Two invalidation strategies: MetadataStrategy (git-diff based via watskeburt/getSHA -- fast, no file reads, just asks git what changed since last SHA) vs ContentStrategy (per-file checksum comparison -- works without git but has to hash every file). MetadataStrategy is the better fit for ARCLUX since packages/git and packages/watcher already do git-diff based watching -- reuse that instead of hashing.
* Cache format has an explicit CACHE\_FORMAT\_VERSION constant (numeric, bumped on breaking changes) so an old cache from a previous ARCLUX version gets safely invalidated instead of returning corrupt/incompatible results.
* Cache dirtiness is scoped precisely: they maintain an explicit list of which CLI options/config changes should 100% invalidate cache vs which only invalidate a subset. ARCLUX equivalent: analyzeRepository(\{repoUrl, branch, ...}) options that affect output (branch, exclude patterns, etc) should be part of the cache key/invalidation check.
* brotli compression at min quality (faster than gzip, still better ratio) used for on-disk cache storage, sync not async (sync is faster in this context and avoids promisifying zlib).

NEXT STEPS for actually implementing packages/cache (not done yet):

1. Design CacheProvider.ts as the orchestrator/interface (equivalent to their Cache class), with a pluggable invalidation strategy (metadata/git-diff-based as primary, matching packages/git's existing capabilities).
2. fileCache.ts, graphCache.ts, repositoryCache.ts likely map to caching ParsedFile results, DependencyGraph results, and Repository (indexed) results respectively at different pipeline stages -- needs confirming against packages/engine/pipeline.ts's actual stage boundaries before finalizing shapes.
3. memoryCache.ts -- in-memory layer (fast, non-persistent), likely sits in front of an optional on-disk persisted cache, need to decide if disk persistence is in scope for v1 or a later phase.
4. Add a CACHE\_FORMAT\_VERSION-style constant so future cache shape changes don't silently return corrupt results to old cache format.
5. Zero code written yet -- this entry is the research/design summary only.

## 2026-08-08 — UPDATE: Graph LOD rendering — implemented

**Status:** Done

Both steps of the LOD plan are done and visually verified in-browser by the user: step 1 (icon gating below zoomScale 0.5) and step 2 (label gating, same threshold, plus always-show labels for high-importance nodes above zoomScale 1.5). Node radius scaling at low zoom (the optional 3rd idea mentioned in the original plan) was not implemented -- current LOD (icon+label gating) was sufficient. Considered done for now; revisit radius scaling later only if a real repo shows it's still needed.

## 2026-08-08 — Cache design: git-diff strategy needs getCommitHistory.ts first

**Status:** In Progress

Follow-up to the earlier cache research entry: confirmed packages/git/getCommitHistory.ts is still an 8-line stub, and there's no existing function anywhere that gets the current commit SHA or lists changed files via git diff. simple-git (the library cloneRepository.ts already uses) can do this easily, but the capability itself doesn't exist yet in ARCLUX. This means the MetadataStrategy-style (git-diff based) cache invalidation approach isn't a 'just plug into packages/git' thing as initially assumed -- getCommitHistory.ts (or a new small function) needs to expose at least: current HEAD SHA, and a way to list files changed since a given SHA. Revised plan: implement that git capability first (or as part of the same PR), before or alongside CacheProvider.ts, rather than assuming it's ready to consume.

## 2026-08-08 — Cache design: shallow clone conflicts with git-diff strategy

**Status:** In Progress

Critical finding: cloneRepository.ts defaults to depth=1 (shallow clone, only the latest commit, no history). This directly conflicts with a MetadataStrategy-style (git-diff based) cache invalidation approach, which needs to diff against a PREVIOUS commit -- with only 1 commit present locally, there's nothing to diff against on a fresh clone.

This doesn't kill the git-diff approach, but changes what it can be used for: it would only work for INCREMENTAL re-analysis of a repo ARCLUX already has a deeper local copy of (e.g. via packages/watcher's ongoing filesystem watch, which presumably keeps a persistent local clone across multiple analysis runs) -- not for a fresh one-shot analyzeRepository() call, which is the common case today (dependency-cruiser assumes it's running against the user's own full local repo, a different situation from ARCLUX cloning someone else's repo fresh each time).

Revised understanding: ARCLUX's cache is more likely to help in two different ways than dependency-cruiser's single git-diff-driven design:

1. Content-hash based caching of PARSE results (ContentStrategy-style, not MetadataStrategy) for repeat analysis of the same repo/branch within a short window -- doesn't need git history at all, just file content hashes, works fine with shallow clones.
2. A git-diff strategy only makes sense later, once packages/watcher is doing persistent incremental watching (a repo checked out once, kept around, re-diffed on change) -- not for the current one-shot clone-analyze-cleanup flow.

Practical next step: prioritize a content-hash based cache (ContentStrategy-style) for packages/cache's first implementation, since it fits ARCLUX's actual current clone behavior. Revisit git-diff based caching once/if packages/watcher's persistent-checkout behavior is implemented -- check packages/watcher's actual design before assuming it keeps repos around long-term.

## 2026-08-08 — packages/cache: 3 of 5 files done

**Status:** In Progress

fileCache.ts (content-hash based, per-ParsedFile), repositoryCache.ts (fingerprint-based, per-Repository), graphCache.ts (same fingerprint, per-DependencyGraph) all implemented and typechecked. Both repositoryCache.ts and graphCache.ts share the same fingerprint scheme (computeRepositoryFingerprint in repositoryCache.ts, derived from sorted FileInfo.hash) so they invalidate together. None wired into engine/pipeline.ts yet -- that's a separate step. Remaining: CacheProvider.ts (orchestrator) and memoryCache.ts (unclear if it's a distinct generic layer or redundant with the three content-hash caches already built -- needs more thought).

## 2026-08-09 — ARCHITECTURE\_MAP.md added: explicit core/extension boundaries

**Status:** Done

Added ARCHITECTURE\_MAP.md defining which packages are CORE (engine, repository, shared -- changes need a decisions.md entry first) vs EXTENSION POINTS (parser, detectors, rules, cache -- safe to add to without discussion) vs FOUNDATION-NOT-WIRED (watcher, incremental). Also defines where AI/intelligence-layer work (semantic search, RAG, embeddings, agent tooling) should live: a new top-level package consuming ARCLUX's structural outputs, not woven into core packages.

Motivated by onboarding ManSio (5th collaborator), whose own project (mscodebase-intelligence) is a much more elaborate codebase-intelligence system -- graph RAG, agentic search, embeddings, LSP bridge, 1000+ tests. Real risk identified: a skilled collaborator coming from an overengineered project has a natural tendency to keep adding intelligence layers to whatever codebase they touch, which could slowly pull ARCLUX away from its current strength (disciplined scope, verified against real repos) toward matching that complexity. This isn't a judgment on ManSio -- his first PR (detectAmbiguousSymbolResolution.ts) was excellent, well-scoped, and followed existing patterns exactly. The boundary is proactive, not reactive to any actual problem yet.

## 2026-08-10 — Vision: TUI (terminal UI) as a third consumer of engine/

**Status:** Not Started

Idea discussed, not started: ARCLUX's engine/ (analyzeRepository()) already returns pure data (DependencyGraph, Repository) decoupled from any UI -- CLI and web dashboard are both just consumers of that same output. A TUI (terminal UI, think lazygit/htop-style keyboard-driven interface) would be a natural third consumer: same engine/, same data, different rendering layer. Candidate library: ink (React for terminal) or blessed, not researched yet. This is long-term vision, not scoped work -- no research done, no library chosen, no timeline. Revisit once core detector/parser coverage feels solid enough to justify a new surface.

## 2026-08-10 — External review from ManSio (MSCodeBase) — 4 findings to verify

**Status:** Not Started

Collaborator ManSio reviewed parsePython.ts + scanFiles.ts, found 4 potential issues, NONE verified yet by us: (1) relative imports with leading dots 'from ..utils import X' may not resolve correctly if tree-sitter strips the dots before resolvePath.ts sees it — highest priority, likely to produce wrong edges on real repos. (2) parsePython.ts has zero test files, unlike Go/Rust parsers. (3) scanFiles.ts has catch\{} blocks that silently drop unreadable files with no warning — same class as the wasm silent-failure gotcha. (4) wasmPath in parsePython.ts is hardcoded to a pnpm-specific node\_modules path (.pnpm/tree-sitter-wasms\@version/...) — will silently fail under npm/yarn installs. Next session: verify each against parsePython.ts/scanFiles.ts directly before fixing anything, per usual verification standard (cat/grep the actual code, don't just trust the review).

## 2026-08-11 — Cytoscape.js researched as UI/UX reference for graph rendering

**Status:** Done

Cloned cytoscape.js (open source, unlike Obsidian which was considered but is closed-source) to research graph visualization patterns. Found src/extensions/renderer/canvas/layered-texture-cache.mjs -- a sophisticated multi-layer texture caching system with zoom-tier caching, per-frame render budgets (deqCost/deqAvgCost), and priority-queue-based texture refresh. Too complex/different (Canvas-based) to adopt wholesale into ARCLUX's SVG renderer, but extracted one directly-applicable principle: don't re-render elements whose own state hasn't changed. Applied as GraphNode.tsx's React.memo wrap. Full layered-caching-style system remains a possible future direction if SVG proves insufficient at larger scale (see the earlier canvas-vs-SVG discussion in this file's history), not pursued now.

## 2026-08-11 — ARCLUX stays structural-truth engine; MSCodeBase-style intelligence becomes an optional consumer layer, not a merge

**Status:** Not Started

Source: GPT roadmap discussion after reviewing ARCLUX architecture map + file sizes, refined further after ManSio suggested keeping the two projects separate. Core decision: do not try to turn ARCLUX into MSCodeBase. ARCLUX stays parse -> index -> graph -> impact -> detect, verifiable and AI-free. Any semantic/RAG/embedding layer becomes packages/intelligence/, sitting ON TOP of Repository + DependencyGraph as a consumer, never inside core. Flow: Repository and DependencyGraph feed packages/intelligence, which then branches into semantic search, context builder, and optional RAG -- core stays the single source of truth underneath all three. Phased roadmap agreed: Phase 0 reliability (python edges -- already fixed this session, wasmPath pnpm-only portability -- still open, silent scan failures in scanFiles.ts -- still open, parser test coverage), Phase 1 structural search (graph-aware search: fuzzyScore.ts + buildImportGraph/ExportGraph/CallGraph already exist, extend GlobalSearch to return structural context -- imports/exports/consumers/routes -- not just filename matches), Phase 2 architecture intelligence (turn the 18 existing detectors into a scored health view -- structural integrity / dependency hygiene / layer consistency percentages derived from real detector output, not AI-generated), Phase 3 optional intelligence (packages/intelligence/ -- embeddings, semantic search, RAG, agent-facing tools -- explicitly deprioritized, not started until Phase 0-2 are solid), Phase 4 external integrations (LSP bridge, MCP/agent interface -- deferred, risk of blurring "who is the source of truth" if added too early).

## 2026-08-11 — LAB 1/2/3 — diff, verify, and analyzeLocal/pipeline merge (all on feat/diff-lab1-mvp, NOT pushed yet)

**Context**: after the ARIES→ARCLUX rename, the temptation was to treat
the original ARIES blueprint (11 parsers, 9-layer event engine, full
web UI) as a from-scratch spec — a "1 year project" framing. Decision
made instead: ARCLUX already has \~80% of the needed machinery built.
Cut scope into 3 small LABs that prove the existing pipeline works,
rather than building new abstractions speculatively. See
roadmap.md's "Core Principle" — this is that principle applied to
process, not just architecture.

### LAB 1 — `arclux diff <refA> <refB> [repoPath]`

New files: `packages/diff/types.ts`, `gitDiff.ts`, `architecturalDiff.ts`,
`apps/cli/diff.ts`. Registered in `apps/cli/index.ts`.

**Honest scope limit, documented in architecturalDiff.ts's own header**:
this does NOT build two separate dependency graphs (one at refA, one at
refB) and diff them — that would require checking out each ref into a
clean state and running the full pipeline twice, not built. What it
DOES do: get changed-files list from `git diff --name-status` (cheap),
then run existing `traceConsumers` against the CURRENT working tree for
each changed file that still exists. Answers "what's affected by files
that changed" using today's graph, not "did the graph itself differ."
Upgrading to true dual-graph comparison is a separate, larger task.

Verified working: tested against ARCLUX's own repo, `HEAD~5 HEAD`
correctly traced GraphFocusView\.tsx/GraphProvider.tsx changes to 9
consumer files (GraphCanvas, GraphViewport, etc).

### LAB 2 — `arclux verify [path]`

New file: `apps/cli/verify.ts`. Registered in `apps/cli/index.ts`.

Combines the same 10 detectors `doctor.ts` runs with `packages/rules/
RuleEngine.ts`'s `runRules()`, into one PASS/FAIL verdict (exit code 0/1).

**Important finding, confirmed by direct inspection (not assumed)**: of
13 rule files across nextjs/react/nestjs/express/vite/electron, only
`packages/rules/nextjs/requirePage.ts` (60 lines) is actually
implemented. The other 12 are copyright-header-only stubs — 8 lines,
zero `export` statement. `verify.ts`'s header comment documents this
explicitly so a future session doesn't assume they're wired in.
Rule/detectedFrameworks string matching confirmed working correctly
(tested: `frameworks checked: nextjs, react` matched
`appliesToFramework: "nextjs"` on requirePage without any mismatch).

**Still not built**: severity policy is minimal — any detector finding
OR any rule *error* fails the build; rule *warnings* are shown but
don't fail it. Not yet tuned against real usage.

### LAB 3 — merge `analyzeLocal.ts` into `pipeline.ts`

This one wasn't a new feature — it closed a duplication gap that
`analyzeLocal.ts`'s own header comment had already flagged ("once
\[pipeline.ts] refactor lands, THIS FILE should be deleted"). Confirmed
via direct read that no parallel-session refactor had actually landed
before doing this (git log showed no such merge to main).

**Real bug found and fixed as a side effect of the merge**:
`analyzeLocal.ts`'s `ensureParsersRegistered()` only registered
`parseTs` and `parsePython` — 2 of pipeline.ts's 7 language parsers.
Every CLI command (`diff`, `verify`, `doctor`, `impact`, `graph`,
`analyze`, `config`) was silently never parsing JS/JSX/CommonJS/Go/Java
files, only TS and Python, since analyzeLocal.ts was CLI's only entry
point. Post-merge, all CLI commands go through the same
`ensureParsersRegistered()` as the web/API remote flow (all 7 parsers +
9 manifest parsers).

**Also removed**: `packages/engine/analyzeRepository.ts` — was a
copyright-header-only empty stub, confusingly named almost identically
to the real `analyzeRepository()` function that lives in `pipeline.ts`.

**API change**: `analyzeRepository()` in pipeline.ts now takes
`{ repoUrl }` OR `{ localPath }` (throws if both or neither given).
Remote-URL code path (`analyzeRemoteRepository`) is a direct extraction
of the pre-existing clone→index→graph→cache→cleanup logic — unchanged
behavior, just moved into its own function so the public
`analyzeRepository()` can route to it or to the new
`analyzeLocalPath()`. Web app / `/api/analyze` callers unaffected
(still call with `{ repoUrl, branch }`, same shape as before).

**No caching for local-path analysis** — deliberate, not an oversight.
`repositoryCache.ts`/`graphCache.ts` are keyed by repoUrl+branch, which
doesn't map to a bare local directory a developer is actively editing.
Documented as a possible future gap if `arclux` commands feel slow on
large local repos — not ruled out, just not built.

Verified: `tsc --noEmit` clean (zero errors) after the merge touched 9
files (pipeline.ts + 7 CLI commands + 2 deletions). `verify`/`diff`/
`doctor` all re-tested working post-merge; detector counts shifted
slightly (428→430 in `apps/web`) as expected from the additional files
now in scope, not a regression.

### Current state

All 3 LABs committed to `feat/diff-lab1-mvp`, 3 commits
(`43013a75`, `ccd1d5b8`, `3926d61f`). **Not pushed to origin.** Branch
is local-only, awaiting review before any push/PR.

### Open follow-ups, not done yet (don't assume these are handled)

* True dual-graph diff (LAB 1's documented scope limit)
* ~~12/13 empty rule stubs (LAB 2) — only requirePage is real~~ — **DONE 2026-08-13**: all 10 stubs implemented,
  13 rules wired into verify.ts/contract.ts; only react/requirePropsTyping remains a documented deferral
* Verify's severity policy is untuned (LAB 2)
* No cache for local-path analysis (LAB 3) — fine for now, revisit if
  slow on large repos

## 2026-08-11 — PLANNED (not yet built) — LAB 4/5/6: Stable Core Contract, Engine/API boundary, External consumers

**Status: planning only, ZERO code written.** This session (that built
LAB 1/2/3 — see the entry above) ran near its context limit right as
this was being scoped out, so it's recorded here as a plan for the
*next* session to execute with full context, rather than started and
left half-finished. Same discipline as the earlier "graph node visual
impact indicator" plan in this file — plan fully, execute in a
dedicated pass.

**Do not skip straight to writing code from this plan.** The
instruction that came with it, verbatim in spirit: map the current
system before adding anything new.

```
CURRENT ARCLUX
      ↓
existing engine
      ↓
existing graph
      ↓
existing impact
      ↓
existing detectors
      ↓
existing CLI
      ↓
NEW BOUNDARIES
      ↓
diff / verify / policy
```

i.e. whoever picks this up should re-read pipeline.ts, graph/, impact/,
detectors/, and the current CLI commands (including diff.ts and
verify.ts from LAB 1/2) FIRST, confirm what's really there the same
way LAB 1-3 did (direct file reads, not assumptions from old docs),
THEN design the boundary — not the reverse.

### LAB 4 — Stable Core Contract

Goal: a single stable shape for `AnalysisResult`, `Graph`, `Impact`,
`Issue`, `Rule` that CLI/API/future consumers all depend on, instead of
each command reaching into `packages/*` internals directly (current
state: e.g. `verify.ts` imports 10 individual detector functions +
`RuleEngine` directly — works, but every consumer re-does this
wiring).

### LAB 5 — Engine/API boundary

```
CLI
 ↓
Engine
 ↓
Core
```

Formalize that CLI (and eventually API, IDE, etc — see LAB 6) only
talks to an Engine layer, never to `packages/parser`, `packages/graph`,
`packages/detectors` etc directly. `pipeline.ts`'s `analyzeRepository()`
is already close to this for the analyze step (post-LAB-3 merge) — LAB
5 is likely about extending that same pattern to diff/verify/detect,
not introducing a new mechanism.

### LAB 6 — External consumers

CLI, IDE, CI, SDK — multiple consumers of the same Engine boundary from
LAB 5. Not started, no design yet beyond the name.

### Ground rule carried over from LAB 1-3, still applies

* Experiment on a branch (`feat/*` or similar), never touch `main`
  directly.
* `main` stays exactly at `origin/main` until a reviewed PR merges —
  confirmed true as of this entry: LAB 1/2/3 sat on
  `feat/diff-lab1-mvp`, local-only, for the entire time they were being
  built, and were only pushed (as a branch, not to main) once the user
  explicitly decided to.
* The workflow that worked for LAB 1-3 and should repeat for LAB 4-6:
  code → test → (optionally) benchmark → if it breaks, reset the branch
  and try again. Cheap to experiment because main is never at risk.
* Confirm real file state before writing code (grep/cat, not memory of
  old docs) — this caught the rule-stub gap (LAB 2) and the 2-vs-7
  parser bug (LAB 3). Assume more gaps like that exist elsewhere.

## 2026-08-12 — LAB 4 MVP built: runAllChecks() stable contract

**Status:** In Progress

packages/engine/contract.ts added on feat/diff-lab1-mvp (still local/branch-only, not merged to main). Wraps the 10 detectors + requirePage rule verify.ts already runs into one runAllChecks(repository) call returning normalized Issue\[] (source/checkId/severity/message) instead of consumers importing 10 detector functions individually. Hit one signature mismatch during build: runRules() needs a 3rd detectedFrameworks arg, pulled from repository.meta.detectedFrameworks. tsc clean after fix. Not yet wired into verify.ts itself (that's LAB 5's job, formalizing CLI -> Engine -> Core boundary) -- this session only built the contract, not the migration.

## 2026-08-12 — LAB 4 — External repository validation + Python parser WASM fix

**Status: Verified — pushed to `feat/diff-lab1-mvp`**

LAB 4 Stable Core Contract was validated against an external repository rather
than only ARCLUX itself. The test target was Django, cloned locally at
`~/django`.

### External repository test

Initial ARCLUX analysis of Django:

* 3,039 modules indexed
* 3,039 graph nodes
* Initial graph contained only 3 edges

This exposed a real Python analysis problem: Django is predominantly Python,
so a 3-edge graph for a 3,039-module repository was clearly suspicious.

### Root cause investigation

Direct inspection confirmed:

* `parsePython` exists and is registered by `engine/pipeline.ts`.
* `LanguageParser` correctly exposes Python through `.py`.
* `resolvePath.ts` already contains Python-specific resolution for:
  * `__init__.py`
  * sibling imports
  * explicit relative imports
  * dotted Python module paths
* Direct invocation of `parsePython` initially returned zero imports with:

`ENOENT: ... tree-sitter-wasms/.../tree-sitter-python.wasm`

The WASM file itself was confirmed to exist inside the pnpm store.

`require.resolve("tree-sitter-wasms/out/tree-sitter-python.wasm")`
also confirmed the correct runtime path.

### Fix

`packages/parser/python/parsePython.ts` contained an incorrect WASM
path calculation.

The path was corrected to resolve the pnpm-installed
`tree-sitter-wasms@0.1.13` Python grammar from the actual ARCLUX working
directory.

No parser architecture was redesigned. The fix was isolated to the runtime
WASM path.

### Parser verification

Direct Python parser execution against:

`django/db/models/base.py`

successfully returned Python imports with no warnings.

Examples observed included:

* `collections`
* `functools`
* `itertools`
* `asgiref.sync`
* `django`
* `django.apps`
* `django.conf`
* `django.core`
* `django.core.exceptions`
* `django.db`
* `django.db.models`
* `django.db.models.constants`
* `django.db.models.deletion`
* `django.db.models.expressions`
* `django.db.models.fetch_modes`
* `django.db.models.fields.composite`
* `django.db.models.fields.related`

### Full Django graph verification

After the fix:

* 3,039 modules indexed
* 3,039 graph nodes
* **7,734 dependency edges**

This is a major validation change from the previous 3-edge result and
demonstrates that the Python parser and Python path-resolution machinery are
actually participating in full-repository graph construction.

### LAB 4 contract verification

`runAllChecks(repository)` was also executed against Django.

Observed result:

* modules: 3,039
* issues: 11,452
* errors: 11,452
* warnings: 0
* passed: false

Issue distribution observed:

* unusedExports: 7,788
* orphanFiles: 2,453
* ambiguousSymbolResolution: 522
* largeModules: 327
* sharedModules: 225
* circularDependency: 104
* deadCode: 24
* duplicateModules: 9

These results are recorded as detector output, not automatically interpreted
as proof that Django itself is defective. The purpose of this test was to
validate that ARCLUX can process a large external Python repository and
produce normalized analysis/check output.

### Important engineering finding

The first failed Django analysis was not treated as a successful test.
The unusually low graph edge count triggered investigation.

The workflow was:

1. Analyze external repository.
2. Detect suspicious result.
3. Inspect parser registration.
4. Inspect Python parser.
5. Test parser directly on a real Django file.
6. Trace failure to missing WASM path.
7. Confirm WASM package actually exists.
8. Fix only the path resolution.
9. Re-run the full repository analysis.
10. Confirm graph increased from 3 edges to 7,734 edges.
11. Run the stable check contract against the external repository.

This confirms the LAB process is capable of finding real integration bugs
that would not necessarily appear from TypeScript compilation alone.

### Commit

`5e3748a7 fix: resolve Python tree-sitter wasm path`

Branch:

`feat/diff-lab1-mvp`

Remote branch was confirmed synchronized with the local HEAD after push.

### Current conclusion

LAB 4 is not merely a type/interface exercise.

The stable Engine contract was exercised against a real, large,
external Python repository and the test uncovered and fixed an actual
runtime integration bug in the existing Python parser.

main

## 2026-08-14 — Use Linux workqueue.c as reference for scheduler design, not implementation

**Status:** Done

When implementing packages/scheduler/\*, cloned torvalds/linux (sparse-checkout, kernel/workqueue.c + include/linux/workqueue.h only) to study a battle-tested design for job scheduling. Chose to port design CONCEPTS only (max\_active concurrency cap, priority-jumps-queue, delayed/deferred execution, ordered-strict-FIFO mode), not any code or CPU/hardware-specific mechanics (NUMA pod affinity, cpumask, per-CPU worker pools, bit-packed work\_struct flags) -- those solve hardware scheduling problems ARCLUX's single-process Node scheduler doesn't have. This is the second time this session external OSS source was read-only referenced for a design pattern rather than copied (see ESLint report-translator attempt, which failed due to sparse-checkout misconfiguration, for the diagnostics/ErrorLocation.ts locationPrecision pattern).

## 2026-08-13 — Platform layer scaffold added

docs/log-today-progress-v2

> **\[STATUS UPDATE, 2026-08-13]: this plan is now implemented.** See "UPDATE: Platform layer scaffold added — implemented — implemented" below.

> **\[STATUS UPDATE, 2026-08-13]: this plan is now implemented.** See "UPDATE: Platform layer scaffold added — implemented — implemented" below.

> **\[STATUS UPDATE, 2026-08-13]: this plan is now implemented.** See "UPDATE: Platform layer scaffold added — implemented" below.

ARCLUX.main

**Status:** Not Started

Added scaffold-only folder structure for a new additive platform layer (runtime, services, scheduler, environment, workspace, terminal, storage, networking, security, system, diagnostics, editor, language, orchestration) plus matching apps/cli commands and apps/web API routes. Files are Apache-header-only stubs, no logic yet. Per ARCHITECTURE\_MAP.md, this consumes existing core (engine/graph/impact/parser) rather than duplicating it — implementation to follow incrementally.

docs/log-today-progress-v2

## 2026-08-13 — UPDATE: Platform layer scaffold added — implemented — implemented

**Status:** Done

Rencana awal baru sebagian ke-scaffold: package runtime, services, scheduler, environment, workspace, terminal, storage, networking, security, system, diagnostics, editor, language, orchestration sudah ada, tapi ketinggalan packages/kernel, packages/semantic-diff, packages/notifications, packages/package-manager, dan CLI command health.ts + package.ts. Semua sudah ditambahkan (stub-only). Juga ditambahkan docs-site/map/map-packages-platform.mdx section Blueprint Integration yang memetakan alur editor dan semantic-diff pipeline ke file platform layer beserta dependency ke engine yang sudah ada. Masih open: belum ada logic diisi, murni struktur file + dokumentasi peta dependency.

## 2026-08-13 — UPDATE: Platform layer scaffold added — implemented — implemented

**Status:** Done

Rencana awal baru sebagian ke-scaffold: package runtime, services, scheduler, environment, workspace, terminal, storage, networking, security, system, diagnostics, editor, language, orchestration sudah ada, tapi ketinggalan packages/kernel, packages/semantic-diff, packages/notifications, packages/package-manager, dan CLI command health.ts + package.ts. Semua sudah ditambahkan (stub-only). Juga ditambahkan docs-site/map/map-packages-platform.mdx section Blueprint Integration yang memetakan alur editor dan semantic-diff pipeline ke file platform layer beserta dependency ke engine yang sudah ada. Masih open: belum ada logic diisi, murni struktur file + dokumentasi peta dependency.

## 2026-08-13 — UPDATE: Platform layer scaffold added — implemented

Rencana awal (lihat entry "Platform layer scaffold added" di atas) baru
sebagian ke-scaffold saat pertama kali dibuat: package `runtime`,
`services`, `scheduler`, `environment`, `workspace`, `terminal`,
`storage`, `networking`, `security`, `system`, `diagnostics`,
`editor`, `language`, `orchestration` sudah ada, tapi ketinggalan:
`packages/kernel/` (Kernel, ProcessTable, SignalBus, ServiceRegistry,
introspection), `packages/semantic-diff/` (SemanticDiff, SymbolDiff,
AstDiff, DependencyDiff, DiffRenderer), `packages/notifications/`
(NotificationManager, Notification, NotificationChannel),
`packages/package-manager/` (PackageManager, PackageManifest,
PackageResolver, PackageState), dan CLI command `health.ts` +
`package.ts`.

Semua sudah ditambahkan (stub-only, sama seperti scaffold awal — Apache
header + comment placeholder, belum ada logic). Juga ditambahkan
`docs-site/map/map-packages-platform.mdx` section "Blueprint
Integration" yang memetakan setiap tahap alur editor (developer ketik →
incremental analysis → diagnostics → impact → notification) dan
semantic-diff pipeline (text→symbol→AST→dependency→impact→
architectural) ke file platform layer yang sesuai, plus dependency-nya
ke `packages/engine/`, `packages/parser/`, `packages/diff/`,
`packages/impact/` yang sudah ada. Masih open: belum ada satupun logic
diisi, ini murni struktur file + dokumentasi peta dependency.
ARCLUX.main

## 2026-08-14 — Issue #6 decision: coarse watchRepository first, true per-file incremental deferred

**Context:** packages/incremental (Cell/Query/Database, salsa-inspired)
and packages/watcher are built and verified standalone but not wired
into the real pipeline; buildIndex does a full rebuild on every call.
Issue #6 asked to wire them in.

**Decision (owner-approved 2026-08-14):** do the coarse version —
`watchRepository` wraps `analyzeRepository({ localPath })` in one
Cell/Query pair keyed on a change-batch revision token. ANY change in
the tree invalidates the whole cached analysis (full re-run), so the
only real win is skipping re-analysis when nothing changed. This is
what watchRepository already does (import fixed to the pipeline API in
this session). True per-file granularity (only re-parse changed files,
reuse cached ModuleInfo) would require rewriting buildIndex itself
around Cell/Query internally — a much larger change, deliberately
out of scope. Revisit if `arclux` commands feel slow on large local
repos.

**Alternatives rejected:** (A) close #6 with no code — rejected because
watchRepository is now functional and cheap to keep; (B) rewrite
buildIndex around Cell/Query now — rejected as too large/risky while
new parsers (PHP) and rules are landing; risks conflicting with the
current pass structure.

## 2026-08-14 — Call graph: TS-parser call extraction is a follow-up

> **\[STATUS UPDATE, 2026-08-14]: IMPLEMENTED same-day — see
> progres/status-core.md "Issue #50 follow-up done: TS-family call
> extraction (issue #316)". `parseTs.ts` runs `extractCalls()`; arclux
> self-repo call edges went 0 → 220.**

**Context:** issue #50 implemented `extractCallsJs` per its spec — wired
only into the JS-family parsers (parseJs/parseJsx/parseCommonJs). The
arclux repo itself is TS-only, so buildCallGraph yields 0 call edges on
it (verified; JS fixtures produce edges correctly).

**Decision:** keep the issue's spec (bare-identifier calls in JS) as the
v1 scope. Wiring an equivalent `extractCallsTs` into parseTs (and the
other TS-family parsers) is a separate, smaller follow-up — file it as
an issue when a contributor picks it up. Known limitations documented
in the code: default-imported callees unresolvable, `obj.foo()` /
`this.foo()` not captured (AST-only, no type info).

## 2026-08-14 — OWP-lens hardening: eligible\_seen, negative controls, crash isolation

**Context:** external review (OWP-class analysis, Tom Jones's
`eligible_seen` / population-rot framing) identified three real gaps in
ARCLUX: (1) no committed negative controls for 7 of 19 detectors — only
prose-documented manual "positive control" verifications;
(2) buildIndex silently dropped unparseable files — no scan-vs-parse
accounting, so "0 Python modules" was indistinguishable from "Python
files were skipped"; (3) one detector throwing would kill the whole
doctor/verify suite and look like "no findings".

**Decision:**

* **Guard inventory (negative controls):** `tests/guard-inventory.test.ts`
  — known-bad + clean fixtures for detectDeadCode/detectDuplicateModules/
  detectEntryPoints/detectIndexFiles/detectLargeModules/
  detectLayerViolation/detectSharedModules (the 7 without committed
  tests). All 19 detectors now have committed positive controls; the
  coverage matrix lives in tests/README.md.
* **Population rot guard (eligible\_seen):** `ScanSummary` in
  shared/types.ts — filesScanned/filesParsed/filesSkippedNoParser/
  skippedByExtension, populated by buildIndex pass 1, carried on
  Repository, surfaced in AnalyzeRepositoryResult (/api/analyze) and the
  analyze CLI line. Consumers can now see how much of the repo made it
  into the graph and WHY the rest didn't.
* **Structural-death guard:** `safeRun` in runDoctor wraps every detector
  (DETECTOR CRASHED finding instead of killing the suite); runAllChecks
  in contract.ts got the same try/catch isolation.

**Alternatives rejected:** a meta-test that mechanically asserts every
detector is covered (fragile — can't introspect "meaningful" coverage);
a runtime registry of eligible sources (over-engineered for a static
analyzer — the scan summary covers the population-rot concern).
ARCLUX.main

## 2026-08-14 — packages/change: new protocol for applying changes, not workstation duplicate

**Status:** In Progress

GPT proposed packages/workstation/ duplicating existing editor/language/terminal/scheduler packages. Corrected to: keep existing high-level packages as-is, add packages/change/ (ChangePlan, PatchSet, ChangeExecutor) as a new cross-cutting protocol any caller (editor, CLI, future assistant) can use to propose+apply changes. ChangeExecutor writes via storage/RecoveryManager.writeTransactional(), not fs directly. Scaffolded minimal 3-file vertical slice first; PatchPreview/ChangeVerifier deferred until a real caller needs them.
