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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .claude/commands/audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ Work every finding in the same run:
- **Decision-residue** (privacy copy, LICENSE text, deleting user-created content,
product-behavior questions) — never auto-edit; goes to the report's `## Residue` with a
recommendation.
- Drift genuinely unfixable this session → ROADMAP `bug` line tagged `#docs` AND a
residue entry.
- Drift genuinely unfixable this session → an entry in `docs/roadmap/dev-workspace.md`
under `## knowledge` (`ROADMAP.md` → "Filing an item") AND a residue entry.

### 5. Roadmap verification

Expand All @@ -109,9 +109,7 @@ under `## Roadmap`. Then:

Dedup near-identical items across area files by hand (one entry, one report, keep the
older date in the report's history line). Filing rule and grammar: the bottom of
`ROADMAP.md` and the spec, `docs/active/specs/2026-09-01-roadmap-restructure-design.md` §2.
Until the migration lands (`docs/roadmap/` absent) the tool prints one line and exits 0 —
do the old manual check against `ROADMAP.md` in that case.
`ROADMAP.md` and the spec, `docs/archive/specs/2026-09-01-roadmap-restructure-design.md` §2.

### 6. Gardening (the anti-rot pass)

Expand Down
2 changes: 1 addition & 1 deletion .claude/hooks/roadmap-edit-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const r = spawnSync(process.execPath, [script, '--structure', '--quiet', '--root
if (r.status === 0) process.exit(0);
process.stderr.write(
'roadmap-check: the roadmap file you just wrote has structure errors — fix them now '
+ '(entry grammar: docs/active/specs/2026-09-01-roadmap-restructure-design.md §2; '
+ '(entry grammar: docs/archive/specs/2026-09-01-roadmap-restructure-design.md §2; '
+ 'filing rule: the bottom of ROADMAP.md)\n' + (r.stdout || '') + (r.stderr || ''),
);
process.exit(2);
6 changes: 3 additions & 3 deletions .claude/rules/android-runtime.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
paths:
- "**/app/**"
last_verified: 2026-07-15
last_verified: 2026-09-01
verify:
- path: youcoded/app/src/main/kotlin/com/youcoded/app/runtime/DirectShellBridge.kt
contains: "no 600ms Enter-split here"
Expand All @@ -27,12 +27,12 @@ Claude Code (a Node CLI) runs inside a Termux-derived environment. **Full contex

## Exec permissions & git auth
- **`~/.claude-mobile/exec-wrappers/*` must be chmod 0755, not 0700.** Java's `setExecutable(true)` gives 0700 under Android's 0077 umask; shebang exec via `/system/bin/sh` then fails EACCES (stricter than a direct linker64 invoke) — breaks `gh` spawning `git`. Fix: `setReadable(true,false)` + `setExecutable(true,false)` in `deployBashEnv()`. Don't "tighten" back — wider perms stay inside the uid-isolated app sandbox.
- **Git HTTPS auth uses `~/.netrc`, NOT `gh auth setup-git`** (Go's raw-syscall exec can't traverse the exec-wrapper path). The OAuth token is mirrored into `~/.netrc` (mode 0600) by `Bootstrap.syncGhTokenToNetrc()` at session-start + the `gh` wrapper's `_youcoded_sync_gh_netrc` post-hook. Add any new gh-auth-changing command to that hook's case list. **Do NOT reintroduce `gh auth setup-git` anywhere** — it fails silently or EACCES.
- **Git HTTPS auth uses `~/.netrc`, NOT `gh auth setup-git`** (Go's raw-syscall exec can't traverse the exec-wrapper path). The OAuth token is mirrored into `~/.netrc` (mode 0600) by `Bootstrap.syncGhTokenToNetrc()` in first-run `Bootstrap.setup()` + the `gh` wrapper's `_youcoded_sync_gh_netrc` post-hook. Add any new gh-auth-changing command to that hook's case list. **Do NOT reintroduce `gh auth setup-git` anywhere** — it fails silently or EACCES.
- **`gh auth login --web` polling is flaky — retry once** if it dies "error connecting to github.com" (Go HTTP/2 on Android's stack, ~1-of-3 success in the wild). Don't wrap a retry in `gh()` (double-prompts a new device code).

## Build-type parity (R8) — guard: `./gradlew :app:assembleReleaseTest` (CI: `android-ci.yml`)
- **Release enables R8 minification; debug skips it — they are NOT equivalent.** **Don't use string-based reflection against your own code** (`getMethod`, `Class.forName`, `KClass`, `::declaredMembers`) — R8 obfuscates the name and the lookup throws. The `PluginInstaller.buildEnv()` reflection bug (`912f5ca7`) shipped a stripped env without `LD_PRELOAD` in release — every marketplace install died — while every dev/CI build was debug. Direct calls always; unavoidable reflection needs an explicit `-keep` in `proguard-rules.pro`, never a silent `try{reflection}catch{fallback}`.
- **`Bootstrap` has a defensive `-keep` rule** — don't remove without an audit confirming nothing reflects against it. **`assembleReleaseTest`** (same R8 config, debug keystore, `.releasetest` suffix, port 9961) is the parity check — run it before tagging after touching reflection/annotation/symbol-name-dependent code. Android workflows `setup-node@v4` explicitly so `bundleWebUi` doesn't depend on the runner image's node.
- **`Bootstrap` has a defensive `-keep` rule** — don't remove without an audit confirming nothing reflects against it. **`assembleReleaseTest`** (same R8 config, debug keystore, `.releasetest` suffix, port 9961) is the parity check — run it before tagging after touching reflection/annotation/symbol-name-dependent code. Android workflows `setup-node@v7` explicitly so `bundleWebUi` doesn't depend on the runner image's node.

## PTY writes are NOT symmetric across the two bridges
- **`PtyBridge.writeInput` keeps the 600 ms split before Enter; `DirectShellBridge.writeInput` deliberately does NOT — never "parity fix" it.** The split works around Ink's 500 ms `PASTE_TIMEOUT` in Claude Code's TUI; `DirectShellBridge` talks to raw bash, which has no paste-mode timing. The shared-env rule above is about `buildRuntimeEnv`/`deployBashEnv`, not write timing. *Guard:* the WHY comment at `DirectShellBridge.writeInput`.
Expand Down
6 changes: 3 additions & 3 deletions .claude/rules/artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ paths:
- "**/desktop/src/renderer/state/artifact-tool-use-tracker.ts"
- "**/desktop/src/renderer/state/ArtifactContext.tsx"
- "**/desktop/src/shared/artifacts/**"
last_verified: 2026-08-30
last_verified: 2026-09-01
verify:
- test: youcoded/desktop/tests/artifacts/artifact-tool-use-tracker.test.ts
- path: youcoded/desktop/src/main/artifacts/artifact-store.ts
Expand Down Expand Up @@ -67,7 +67,7 @@ Per-project sidecars + a central index track every file Claude touches; I/O is m

## Paths & counts
- **Project list = saved folders (`youcoded-folders.json`), NOT the central index.** `buildSavedFolderProjects` reuses an index entry by canonical path, else synths one whose `id` IS the path (traversal-guarded).
- **Two single-source count helpers:** `countArtifacts` vs `countAllFiles` — never recompute inline (282-vs-1209 drift). Both subtract orphans via ONE cwd-keyed cache (`useMissingArtifacts.ts`), never cleared before its replacement lands.
- **Two single-source count helpers:** `countArtifacts` vs `countAllFiles` — never recompute inline. `countArtifacts` drops orphans (`fs.access`); `countAllFiles` is raw discovery (`artifacts/projects-index.ts`).

## Concurrency
- **`casWrite` uses a mkdir-based lock** (bare CAS = TOCTOU data loss); central-index writers use `mutateFileUnderLock`. `appendVersion` retries CAS 5× — never add a second loop.
Expand All @@ -85,4 +85,4 @@ Per-project sidecars + a central index track every file Claude touches; I/O is m
- **Drawer state is per-session keyed by `sessionId`**, labels SESSION-scoped; layout-level, not an overlay. Status glyphs (`●◐○`) BANNED. `.youcoded/` auto-gitignored.
- **`showDeletedArtifacts` is SESSION-DRAWER-ONLY — deliberate** (a tombstone, not a recovery path). Cross-device-SYNCED — don't delete the "unused" flag.
- **`EXCLUDE` has NO renderer caller** (legacy round-trip only); in-folder files can't be excluded.
- **Android `get`/`save`/`read-binary` are REAL (SessionService.kt), NOT stubs — mirror any new desktop guard in Kotlin.** List/project/check-existence return `not-implemented-on-mobile`; `project:*` is desktop-only.
- **Android `get`/`save`/`read-binary` are REAL (SessionService.kt), NOT stubs — mirror any new desktop guard in Kotlin.** List/project/import-file/search-content/watch-project return `not-implemented-on-mobile`; `check-existence` stubs "nothing missing"; `project:*` is desktop-only.
11 changes: 6 additions & 5 deletions .claude/rules/engine-local-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ paths:
- "**/desktop/src/main/engine/**"
- "**/desktop/src/main/models/**"
- "**/desktop/test-engine/**"
last_verified: 2026-08-16
- "**/desktop/src/main/providers/provider-registry.ts"
last_verified: 2026-09-01
verify:
- path: youcoded/desktop/src/main/engine/engine-supervisor.ts
contains: "models-dir"
Expand All @@ -21,7 +22,7 @@ verify:

# Local llama.cpp engine + model manager (Plans B + C)

A downloaded, SHA-256-verified `llama-server` in router mode + supervised, plus the in-app model manager (curated catalog, HF search, resumable downloads, GPU-aware fit). **READ `youcoded/docs/engine-dependencies.md` first — every fact below is verified there against b9992. Re-run `test-engine/probe-*.mjs` on every engine bump (any new probe MUST pass `--models-dir`).**
A downloaded, SHA-256-verified `llama-server` in router mode + supervised, plus the in-app model manager (curated catalog, HF search, resumable downloads, GPU-aware fit). **READ `youcoded/docs/engine-dependencies.md` first — every fact below is verified there against b10665. Re-run `test-engine/probe-*.mjs` on every engine bump (any new probe MUST pass `--models-dir`).**

## Engine (Plan B, `src/main/engine/`) — guards: `engine-supervisor.test.ts`, `engine-acquisition.test.ts`, `test-engine/probe-{health,models,chat}.mjs`
- **`--models-dir <cacheDir>` discovers GGUFs — NOT `LLAMA_CACHE`** (vestigial; only `-hf` auto-downloads). Covers bring-your-own GGUFs AND Plan C downloads. Without it, `GET /models` is empty and every completion is 400 `model not found`. Router id = filename minus `.gguf` (== `cache-scan.ts`).
Expand All @@ -33,11 +34,11 @@ A downloaded, SHA-256-verified `llama-server` in router mode + supervised, plus

## Model manager (Plan C, `src/main/models/`) — guards: `model-downloader.test.ts`, `test-engine/probe-download.mjs`
- **Flat-basename cache naming is a probe-pinned contract, single-file AND multi-part** — `model-downloader.ts` writes each HF file under its BASENAME; `probe-download.mjs` asserts the router lists + serves both ids. NEVER rename downloads or change split-part naming without re-running it.
- **Curated list carries NO baked sizes** — the panel computes size + fit LIVE from `models.quants(hfRepo)` (lazy per tier, per-card `loading|ready|unavailable`). Remote list is `schemaVersion`-gated with a shipped-copy fallback. Don't re-add baked sizes.
- **Curated list carries NO baked sizes** — the panel computes size + fit LIVE from `models.quants(hfRepo)` (lazy per tier, per-card `idle|loading|error`). Remote list is `schemaVersion`-gated with a shipped-copy fallback. Don't re-add baked sizes.
- **Fit is GPU-AWARE with a safety bias** — VRAM only UPGRADES a verdict, and only for a confidently-probed DEDICATED GPU; integrated GPUs fall back to RAM-only. Windows uses registry `qwMemorySize` / `nvidia-smi`, NEVER `Win32_VideoController.AdapterRAM` (caps at 4 GB).
- **The quant parser DENYLISTS `mmproj*` + `mtp-*` aux files and recognizes `MXFP4(_MOE)`.** Multi-part sets must be COMPLETE before download. Unrecognized tokens drop silently.
- **Delete unloads best-effort, then removes every part + `.partial`.** CUDA opt-in is Windows-x64-only. `engine:set-context` restart nulls `supervisorBinary` (else `rebuildSupervisor` dedups on `binaryPath` and keeps the old `-c`).

- **`listModels()`'s K2 union is LISTING ONLY** — it merges a disk scan into the router's `GET /models` (router rows win), so a disk-only row is a selectable model the router CANNOT serve. Serveability is separate: `ensureServable` (rescan once, re-check, **fail OPEN**) at the local-send chokepoint in `provider-registry.ts`, plus `refreshModels()` after every download and delete.
- **`listModels()`'s K2 union is LISTING ONLY** — it merges a disk scan into the router's `GET /models` (router rows win), so a disk-only row is a selectable model the router CANNOT serve. Serveability is separate: `ensureServable` (rescan once, re-check, **fail OPEN**) at the local-send chokepoint in `providers/provider-registry.ts`, plus `refreshModels()` after every download and delete.
- **The router re-scans `--models-dir` only when asked: `GET /models?reload=1`** — a post-boot file 400s `model 'X' not found` until then (measured 2026-08-16). **A WRITE, never a poll** — `load_models()` unloads models whose source changed or vanished. Guards: `engine-supervisor.test.ts` → "router rescan" describe, esp. "the background model poll NEVER sends reload=1".
- **Orphaned `.partial`s: `models:orphaned-partials`** lists them (in-flight excluded via `activePartialNames()`); clean via `models:delete`, resume by re-downloading the same repo+quant. Guards: `cache-scan.test.ts`, `model-downloader.test.ts`. Panel UI = ROADMAP follow-up.
- **A cancelled download keeps its `.partial`** — `models:resume` continues it, `models:delete` removes it, the panel shows a partial row (`models:orphaned-partials` is gone). Guards: `cache-scan.test.ts`, `model-downloader.test.ts`.
11 changes: 7 additions & 4 deletions .claude/rules/harness-evaluator.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ paths:
# defects here that 4,500 passing tests missed, because every test drives a scripted
# fake model and none of them spend a real turn deciding what to do next.
- "**/desktop/src/main/harness/tools/**"
last_verified: 2026-08-13
last_verified: 2026-09-01
verify:
- path: youcoded/desktop/src/main/harness/eval/run-case.ts
contains: "askUser: async"
Expand All @@ -38,6 +38,8 @@ verify:
- test: youcoded/desktop/tests/harness-eval-report.test.ts
- test: youcoded/desktop/tests/harness-review-fixture.test.ts
- test: youcoded/desktop/tests/harness-review-runner.test.ts
- test: youcoded/desktop/tests/harness-eval-orchestrator.test.ts
- test: youcoded/desktop/tests/harness-eval-estimate.test.ts
---

# Harness evaluator (`test-engine/harness-eval.mjs`)
Expand All @@ -51,7 +53,7 @@ free and needs no key; a real run needs `--key-file`.
if `OPENROUTER_API_KEY` is in its environment, and passes worker config over **stdin** —
never argv, never env. `delete process.env.X` is `unsetenv`: in-heap only, it never
rewrites `/proc/<pid>/environ`, which every same-uid descendant — including a Bash call
the model makes — can read. **`review-harness.mjs` still has the bug** (ROADMAP). Guard:
the model makes — can read. **`review-harness.mjs` still has the bug** (a `decision` in `docs/roadmap/dev-workspace.md`). Guard:
`harness-eval-key-leak.test.ts`, whose negative control must report LEAKED.

- **The grader always loads from the orchestrator's own build; only the worker loads the
Expand All @@ -72,8 +74,9 @@ free and needs no key; a real run needs `--key-file`.

- **The fixture jail is held by `askUser`, not `decide`.** `decide` is fully permissive;
`askUser` denies every ask that isn't a genuine `AskUserQuestion` — `external_directory`,
`doom_loop`, `max_steps`. **One path is exempt by design:** Bash's spill root
(`tools/spill-paths.ts`) is `ok`, so a model can read back its own truncated output.
`doom_loop`, `max_steps` (allowed once, `STEP_GATE_ALLOWANCE`). **Exempt by design**
(`tools/guards.ts`): Bash's spill root and `internalReadRoots` — a model may read back its
own truncated output.
Guard: `harness-review-runner.test.ts` → "denies a Write outside the fixture".

- **Uniform step budget, not the app's chat tiers** (25/50 cuts a 40–80-call run short),
Expand Down
17 changes: 9 additions & 8 deletions .claude/rules/landing-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ paths:
- "**/docs/gallery/**"
- "scripts/ui-review/**"
- "**/desktop/src/renderer/dev/workbench/**"
last_verified: 2026-08-28
last_verified: 2026-09-01
verify:
- path: scripts/ui-review/site-assets.sh
contains: "docs/media"
Expand All @@ -15,8 +15,8 @@ verify:
- path: scripts/ui-review/README.md
contains: "Recording a loop"
- path: scripts/ui-review/copy-preview.py
- path: youcoded/desktop/src/renderer/dev/workbench/mock-shim.ts
contains: "__workbenchAppearanceSync"
- path: youcoded/docs/index.html
contains: "Appearance"
- path: youcoded/desktop/src/renderer/dev/workbench/reply-script.ts
contains: "splitTurns"
- path: youcoded/desktop/src/renderer/dev/workbench/fixture-loader.ts
Expand Down Expand Up @@ -69,9 +69,10 @@ Switches: `?seed=none` (empty chat), `?title=`, `?model=`, `?platform=android`,
**Why:** every frame still "verifies" against stale code — filmed the old fixture twice.

## The live embed
**Invariant:** the page's theme button drives the app's real Settings → Appearance; theme
changes go through `__workbenchAppearanceSync` (the app's cross-window sync), never a
reload; the iframe ignores the pointer until the visitor clicks once.
**Invariant:** the page's floating theme button clicks the app's own gear + Appearance row
inside the iframe (the swatch bar and its `__workbenchAppearanceSync` hook went in `8d077dcd`;
the hook survives only for the workbench deck); never a reload; the iframe ignores the
pointer until the visitor clicks once.
**Why:** a reload flashed the poster; an interactive iframe under the wheel trapped page
scroll ("janky").

Expand All @@ -84,5 +85,5 @@ never as a prose description or a still that can't show it.
## Copy and review
**Invariant:** page copy is reviewed in place with `scripts/ui-review/copy-preview.py serve
… [--media docs/media]` (edit text on a page-shaped preview; per-row loop verdicts) — never
a table, contact sheet, or chat description (all rejected). The never-claim list lives in the
spec's Global Constraints; the disclaimer paragraph is verbatim.
a table, contact sheet, or chat description (all rejected). The never-claim list is the audit's,
referenced from the spec's "Not in scope"; the footer's Anthropic non-affiliation sentence is verbatim.
Loading