Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
03cce5c
docs(llm-mobile): capture phase context
slipalison Aug 16, 2026
166b3da
docs(llm-mobile): generate plan (8 tasks, 5 waves)
slipalison Aug 16, 2026
c355daf
chore(llm-mobile): record baseline and open platform support matrix
slipalison Aug 16, 2026
3712d17
feat(llm-mobile): make native backend config pure data per platform
slipalison Aug 16, 2026
2b07202
feat(llm-mobile): add Hy-MT2-1.8B as the default model for new installs
slipalison Aug 16, 2026
afe8d88
feat(llm-mobile): add official Android CPU translation backend
slipalison Aug 16, 2026
a091546
feat(llm-mobile): refuse translation gracefully when unsupported
slipalison Aug 16, 2026
b49b675
feat(llm-mobile): prove the .so ships in the Android APK, promote And…
slipalison Aug 16, 2026
461d828
chore(llm-mobile): avoid a false positive in the mutable-static heuri…
slipalison Aug 16, 2026
24007db
feat(llm-mobile): add iOS generation loop behind a mockable native co…
slipalison Aug 16, 2026
f00142a
feat(llm-mobile): link iOS to a pinned llama.cpp XCFramework and wire…
slipalison Aug 16, 2026
7284743
chore(llm-mobile): mark all 8 tasks completed in PLAN.md
slipalison Aug 16, 2026
8a21bb8
docs(llm-mobile): record execution summary for iteration one
slipalison Aug 16, 2026
b721fda
fix(llm-mobile): drop the deterministically-red iOS CI job, mark T-8 …
slipalison Aug 16, 2026
c029cb3
chore(llm-mobile): fix two provably wrong DoD checks and a stale revi…
slipalison Aug 16, 2026
827b0d6
chore(llm-mobile): let the iOS DoD accept a documented absence
slipalison Aug 16, 2026
29af388
chore(llm-mobile): tie the CI action pins to the actual job count
slipalison Aug 16, 2026
02ef11c
chore(llm-mobile): make the documented iOS absence harder to fake
slipalison Aug 16, 2026
ef1661a
fix(llm-mobile): guard InitializeAsync with a SemaphoreSlim in both t…
slipalison Aug 16, 2026
0ebf8be
docs(llm-mobile): record review, loop history and the warning fix round
slipalison Aug 16, 2026
ad5ab7f
chore(llm-mobile): ship phase (APPROVED_WITH_WARNINGS)
slipalison Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,12 @@ jobs:

- name: Build Android app
run: dotnet build src/TranslateReader/TranslateReader.csproj -c Release -f net10.0-android

# build-ios intentionally absent: the 10 tr_llama_* entry points declared in
# src/TranslateReader/Platforms/iOS/LlamaNativeAccess.cs are not exported by the pinned
# llama.cpp XCFramework (b10453) -- confirmed by inspecting the fetched headers and binary,
# not inferred. On full-AOT iOS this is a deterministic link failure (undefined symbols),
# not a risk that CI would sometimes catch. D-2026-08-16-llm-mobile-10's rule is that a red
# job is never committed because that assumed an *unknowable* outcome from this Windows
# machine; this failure is knowable without macOS, so the job stays out until the native
# symbol layer exists (D-2026-08-16-llm-mobile-12).
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,8 @@ scripts/.zitadel-setup-done
.jdi/specialists.md
.jdi/reviewers.md
.jdi/skills-registry.md

# llama.cpp XCFramework fetched by scripts/fetch-llama-xcframework.sh (D-2026-08-16-llm-mobile-9).
# Pinned by tag + SHA-256 in TranslateReader.csproj, never committed -- this repo has never used
# Git LFS and this artifact is far larger than anything versioned here today.
.cache/llama-xcframework/
39 changes: 24 additions & 15 deletions .jdi/agents/jdi-reviewer-translatereader.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,33 +155,39 @@ Reviewer picks implementation based on active shell. When in doubt, prefer bash

### Gate 1: Build

Windows TFM is the verification target: LLamaSharp backends (Cpu/Cuda12) ship for Windows only
today, and a bare solution build attempts Android/iOS TFMs whose workloads may be absent in
dev/CI. Target the app csproj explicitly — forcing `-f` at solution level fails with NETSDK1005
on the `net10.0`-only Core/Tests projects (REVIEW ci-seguranca W-5). Mobile TFMs are a documented
secondary target (CLAUDE.md § Build) — build them only when the phase touched `Platforms/`.
Windows AND Android are both first-class local build targets (D-2026-08-16-llm-mobile-10):
LLamaSharp ships a Windows backend (Cpu/Cuda12) and, since the `llm-mobile` phase, an official
Android backend (`LLamaSharp.Backend.Cpu.Android`) too, and this machine can build both. Target the
app csproj explicitly for BOTH — forcing `-f` at solution level fails with NETSDK1005 on the
`net10.0`-only Core/Tests projects (REVIEW ci-seguranca W-5).

**bash:**
```bash
dotnet restore && dotnet build src/TranslateReader/TranslateReader.csproj -c Release -f net10.0-windows10.0.19041.0
dotnet build src/TranslateReader/TranslateReader.csproj -c Release -f net10.0-android
```

**PowerShell:**
```powershell
dotnet restore
if ($LASTEXITCODE -eq 0) { dotnet build src/TranslateReader/TranslateReader.csproj -c Release -f net10.0-windows10.0.19041.0 }
if ($LASTEXITCODE -eq 0) { dotnet build src/TranslateReader/TranslateReader.csproj -c Release -f net10.0-android }
```

Failure = block.
Windows build failure = BLOCK. Android build failure = BLOCK — Android is no longer a
missing-workload-tolerant secondary target (that premise is retired by D-2026-08-16-llm-mobile-10);
both TFMs are expected to build at 0 Error(s), and Android is expected at 0 Warning(s) too
(`.jdi/decisions/D-2026-08-16-llm-mobile-4.md` baseline).

**Secondary (only if the phase touched `src/TranslateReader/Platforms/` or a mobile-specific
dependency); a missing workload is reported as WARN, never as BLOCK:**
```bash
dotnet build -f net10.0-android
```
```powershell
dotnet build -f net10.0-android
```
iOS is never a local gate: this machine is Windows without the `maui-ios` workload, so a local
reviewer run does not attempt `net10.0-ios`/`net10.0-maccatalyst` and their absence here is expected,
not a finding.

There is currently NO `build-ios` job in `.github/workflows/ci.yml`, and that is deliberate. It was
removed in `b721fda` because the iOS P/Invoke layer declares `tr_llama_*` entrypoints that no shipped
artifact exports, which makes the link failure deterministic rather than unknown — see
`.jdi/decisions/D-2026-08-16-llm-mobile-12.md`. Do not treat the missing job as a regression, and do
not add one back until the native symbol gap is closed.

### Gate 2: Tests

Expand Down Expand Up @@ -753,7 +759,10 @@ Print REVIEW.md path + final verdict.
- AM scope is empty (no `.cs`/JS change in the phase) -> gate 3 still runs; the floor applies to
whatever `scripts/coverage-gate.sh` measures at HEAD, never SKIPPED
- `dotnet format` unavailable -> warn on gate 4, do not block
- Mobile TFM build fails for a missing workload -> WARN, not BLOCK (Windows TFM is the gate)
- Android build fails -> BLOCK (D-2026-08-16-llm-mobile-10; Android is a first-class gate now, not
a missing-workload-tolerant one). iOS/MacCatalyst are never attempted locally at all (no
`maui-ios` workload on this machine) -> that absence is expected, not a finding; iOS build is
CI-only.
- Phase not executed (no SUMMARY.md) -> abort, suggest /jdi-do
- Windows without Git Bash -> use the PowerShell branch of each gate
- bash + PowerShell both available -> prefer bash (more portable output)
Expand Down
5 changes: 2 additions & 3 deletions .jdi/coverage-waivers.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@
# the gate prints COVERAGE_WAIVER_INVALID and keeps counting the file as a violation.
#
# src/TranslateReader/Pages/SomeNewPage.xaml.cs # D-2026-XX-XX-some-phase-N justification
#
# No blank waiver rows: today's baseline is zero live entries -- the app has not gained a new
# .cs file since the boundary commit 4285f25.

src/TranslateReader/Platforms/iOS/LlamaNativeAccess.cs # D-2026-08-16-llm-mobile-5 pure P/Invoke declarations for the iOS translation backend (zero control flow, >=10 [LibraryImport] calls). The actual generation loop is Business.Engines.LlamaCppTranslationEngine in TranslateReader.Core, unit-tested with NSubstitute over ILlamaNativeAccess; this Windows machine has no maui-ios workload and cannot compile or run this file at all.
30 changes: 30 additions & 0 deletions .jdi/decisions/D-2026-08-16-llm-mobile-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
D-2026-08-16-llm-mobile-1 (2026-08-16): A phase entrega em DOIS BLOCOS sequenciais e "nao quebrar
nada" e requisito de PRIMEIRA CLASSE, medido contra baselines gravados, LOCKED.

Bloco 1 (base + Android) roda ANTES do Bloco 2 (iOS). Bloco 1: configuracao nativa por plataforma,
modelo Apache-2.0 no registry, gating de memoria e degradacao graciosa, backend Android, `.so` no
APK, gates do reviewer corrigidos. Bloco 2: engine iOS com P/Invoke proprio, XCFramework pinado,
job de CI em runner macOS, MacCatalyst tratado.

MOTIVO: os dois blocos tem custo e risco radicalmente diferentes. Tudo do Bloco 1 e verificavel
NESTA maquina (Windows, sem workload `maui-ios`); nada do Bloco 2 e — iOS exige macOS para compilar
e device fisico para executar. Se o Bloco 2 travar, o Bloco 1 continua sendo entrega completa e
comprovavel. O inverso nao existe: comecar pelo iOS arrisca terminar a phase sem NADA verificado.

BASELINES QUE A PHASE PROTEGE (medidos em 2026-08-16 na branch `feat/llm-mobile`; regressao = falha):
- `dotnet test` = 455 passed / 2 skipped / 0 failed. Os 2 skips sao `TranslationEngineTests` que
exigem GGUF real — pre-existentes, nao mexer, nao "consertar", nao converter em falha.
- Build Android Debug `net10.0-android` = 0 warnings / 0 errors.
- Build Windows Release `net10.0-windows10.0.19041.0` = 0 errors.
- Nenhum nome de teste que existe no commit base pode DESAPARECER (checagem `comm -23` nome a nome,
nao contagem — contagem pode ser mascarada por testes novos).

REGRA DE HONESTIDADE: se o Bloco 2 nao fechar, a saida CORRETA e registrar o estado real (o que foi
entregue, onde parou, qual a evidencia) e deixar o iOS para uma phase seguinte. E PROIBIDO inventar
um `Verify:` que passa sem provar, declarar iOS funcionando sem prova, ou repetir estimativa de
pesquisa (tokens/s) como se fosse medicao observada.

CUSTO ACEITO: a phase pode terminar entregando so o Bloco 1, deixando o objetivo declarado
("iOS tambem") parcialmente aberto. Preferimos entrega parcial verdadeira a entrega total declarada
e nao verificada — o app hoje ja nao roda LLM em mobile nenhum, entao Android sozinho ja e ganho
liquido, e um iOS "verde no papel" seria regressao de confianca, nao progresso.
35 changes: 35 additions & 0 deletions .jdi/decisions/D-2026-08-16-llm-mobile-10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
D-2026-08-16-llm-mobile-10 (2026-08-16): Android vira alvo de PRIMEIRA CLASSE nos gates. O agent
`jdi-reviewer-translatereader` e corrigido junto com o codigo, e o job de CI iOS so entra na branch
quando estiver VERDE, LOCKED.

PORQUE FAZ PARTE DA ENTREGA: o Gate 1 do reviewer documenta hoje que "Windows TFM is the verification
target: LLamaSharp backends (Cpu/Cuda12) ship for Windows only today". Essa premissa MORRE nesta
phase. Sem corrigir o agent, o gate validaria menos do que a phase promete — o Android poderia
quebrar e sair com WARN.

DOIS DEFEITOS CONCRETOS A CORRIGIR NO GATE 1:
(a) o comando secundario esta escrito `dotnet build -f net10.0-android` SEM csproj explicito. Isso
falha com NETSDK1005 nos projetos `net10.0`-only (Core/Tests) — exatamente o erro que o proprio
gate ja documenta e evita no comando do Windows. Passa a ser
`dotnet build src/TranslateReader/TranslateReader.csproj -c Release -f net10.0-android`.
(b) o Android e classificado como alvo secundario com falha reportada apenas como WARN. Passa a
BLOCK. O texto "a missing workload is reported as WARN, never as BLOCK" sai; entra
"Android build failure = BLOCK" e "iOS build is CI-only (macOS runner) and is never a local gate".

CI: `.github/workflows/ci.yml` (reusable, chamado por `pipeline.yml`) ganha o job `build-ios` em
runner macOS, instalando `maui-ios` e rodando
`dotnet build src/TranslateReader/TranslateReader.csproj -c Release -f net10.0-ios`, com as MESMAS
actions pinadas por SHA que os jobs existentes usam (convencao do repo, exigida pelo scorecard).
Os jobs `test`, `build` (Windows) e `build-android` ficam intactos.

REGRA DE HONESTIDADE DO JOB: o job iOS so e commitado se passar. Um job vermelho na `ci.yml` quebra o
pipeline de TODO mundo em toda PR — seria regressao real de infraestrutura em nome de marcar um
checkbox. Se o Bloco 2 nao fechar, o job nao entra, o item de DoD correspondente FALHA, e a phase
reporta entrega parcial (D-2026-08-16-llm-mobile-1). Isso e o comportamento desejado, nao um bug do
DoD: um DoD que passa quando o iOS nao funciona nao serve para nada.

CUSTO ACEITO: a existencia do job NAO prova que o build iOS passa — nenhuma maquina desta phase
compila iOS (exige macOS; esta e Windows e nem tem o workload `maui-ios`). O verde so aparece no PR.
Por isso o resultado do job vive em `## Deferred to PR review` e o item auto-verificavel se limita a
provar que o job existe, esta bem formado e nao degradou os outros tres. Marcar AC5 como prova de
que "iOS funciona" seria mentira; marcar a estrutura do job e o maximo verificavel aqui.
44 changes: 44 additions & 0 deletions .jdi/decisions/D-2026-08-16-llm-mobile-11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
D-2026-08-16-llm-mobile-11 (2026-08-16): Dois comandos `Verify:` do DoD desta phase tinham defeito
OBJETIVO, comprovado por medicao independente do reviewer, e foram corrigidos pelo ORQUESTRADOR —
nao pelo doer, e nao para fazer codigo ruim passar. LOCKED.

A regra da phase e "se um `Verify:` reprova, o defeito e do CODIGO — conserte o codigo, nunca o
`Verify:`". Essa regra existe para impedir que o executor afrouxe o criterio ate o proprio trabalho
passar. Ela NAO cobre o caso em que o comando esta objetivamente errado desde que foi escrito, o que
e mensuravel e foi medido. Nos dois casos abaixo o codigo esta correto e o comando e que mentia.
Quem corrigiu foi o orquestrador, com o registro aqui, para que a correcao seja auditavel.

DEFEITO 1 — DoD 9, heuristica de static mutavel medindo contra baseline errado.

O comando exigia `-le 1` ocorrencia. O reviewer rodou o grep IDENTICO em worktree no proprio commit
BASELINE da phase (`166b3da`) e contou **4**, nao 1: `_nativeLibraryConfigured` mais tres
propriedades expression-bodied pre-existentes de `SettingsOverlay.xaml.cs` (`IsDesktopIdiom`,
`ScreenWidth`, `ScreenHeight`) — propriedades computadas, nao estado mutavel, e sem qualquer relacao
com esta phase. O limite `-le 1` portanto ja era falso ANTES de a phase tocar em qualquer arquivo.

O que o item realmente quer provar e "esta phase nao introduziu static mutavel novo". O comando
passa a comparar a contagem em HEAD contra a contagem no BASELINE, em vez de contra um literal
inventado. Medicao apos a correcao: HEAD = 4, BASELINE = 4, delta zero.

Registrado tambem o que o doer NAO fez: ele podia ter "consertado" as tres propriedades legadas do
`SettingsOverlay.xaml.cs` para o numero fechar. Recusou, citando D-2 (fronteira de legado) e
disciplina de escopo de arquivo, e deixou o item reprovando com a explicacao. Foi a conduta correta:
mexer em legado fora de escopo para satisfazer uma heuristica e exatamente o tipo de dano que a
fronteira de legado existe para impedir.

DEFEITO 2 — DoD 7, checagem de binario rastreado casando com o proprio script exigido.

O comando `test -z "$(git ls-files | grep -i xcframework)"` pretende provar que nenhum BINARIO
xcframework entrou no historico do git. Mas ele casa qualquer caminho contendo "xcframework",
inclusive `scripts/fetch-llama-xcframework.sh` — o script que o MESMO item de DoD exige que exista.
O item era autocontraditorio desde o commit `f00142a`: cumprir uma metade reprovava a outra.

A checagem passa a excluir `scripts/`, `.jdi/` e `docs/`, que carregam o nome por referencia textual
e nunca conteudo binario. A intencao original — nenhum binario versionado — segue integralmente
verificada, e os outros 21 sub-checks do DoD 7 continuam intactos, incluindo o comparador de
checksum provado por execucao nos dois sentidos.

CUSTO ACEITO: um `Verify:` corrigido a meio da phase e um `Verify:` que nunca reprovou de verdade
neste ciclo, entao a forca dele so sera exercida em ciclos futuros. Aceitavel porque a alternativa —
manter um comando comprovadamente falso — produziria um BLOCKED permanente e sem sentido, ou pior,
empurraria um executor futuro a danificar codigo legado para satisfazer um numero arbitrario.
66 changes: 66 additions & 0 deletions .jdi/decisions/D-2026-08-16-llm-mobile-12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
D-2026-08-16-llm-mobile-12 (2026-08-16): O Bloco 2 (iOS) desta phase e rebaixado a ENTREGA PARCIAL.
O job `build-ios` sai do `ci.yml` ate a camada de simbolos nativos existir; o Bloco 1 (Android)
permanece entrega completa e provada, sozinho. LOCKED.

CONTEXTO: a revisao da iteracao 1 (`.jdi/phases/llm-mobile/REVIEW.md`, blocker B-1) mediu, no
artefato REAL baixado nesta maquina (`.cache/llama-xcframework/b10453/llama.framework`), que
`tr_llama` nao aparece em NENHUM header nem no binario; `llama.h` real expoe 245 declaracoes
`LLAMA_API`, todas `llama_*`. Reproduzido de forma independente ao corrigir este blocker: `grep -rl
tr_llama .cache/llama-xcframework/b10453/` = vazio (headers e binario), `grep -c LLAMA_API llama.h`
= 245, `git grep -l tr_llama` = exatamente 1 arquivo no repo inteiro. As 10 declaracoes
`[LibraryImport("__Internal", EntryPoint = "tr_llama_*")]` de
`src/TranslateReader/Platforms/iOS/LlamaNativeAccess.cs` (T-8, commit `f00142a`) portanto apontam
para simbolos que nenhum artefato do repo fornece. Em `__Internal` + full AOT iOS, isso e resolvido
no LINK nativo: 10 simbolos indefinidos = falha deterministica (classe MT5210), nao um risco a ser
corrido. O shim C que traduziria `tr_llama_*` para `llama_*` de verdade nao existe em lugar nenhum
do repo -- nem `.c`/`.m`, nem passo de build, nem segunda `NativeReference`. Ele e NECESSARIO por
design: algumas operacoes (`tr_llama_sample_next_token(ctx, temperature)`) nao tem equivalente 1:1
na API real, que exige montar uma sampler chain.

POR QUE ISTO NAO E O CUSTO ACEITO EM D-10: D-10 aceita que "a existencia do job NAO prova que o
build iOS passa" porque nenhuma maquina da phase compila iOS -- o verde e INCOGNOSCIVEL daqui. Este
vermelho e diferente: e COGNOSCIVEL com o artefato que o proprio doer baixou e extraiu, sem precisar
de macOS. A propria D-10 ja previa esta saida, no paragrafo "REGRA DE HONESTIDADE DO JOB": "Se o
Bloco 2 nao fechar, o job nao entra, o item de DoD correspondente FALHA, e a phase reporta entrega
parcial (...). Isso e o comportamento desejado, nao um bug do DoD." Esta decisao EXECUTA essa
clausula; nao a reabre, nao redecide D-5/D-9/D-10.

O QUE MUDA:
- `.github/workflows/ci.yml` perde o job `build-ios` (unico job removido; `test`, `build`,
`build-android` intactos, mesmas actions pinadas por SHA, sem tab no YAML).
- `docs/NATIVE-BACKENDS.md`: `PLATFORM ios` passa de `UNVERIFIED` para `UNSUPPORTED` -- reflete que
o binario NAO linka, nao so "nao foi executado aqui". `UNVERIFIED` teria dito "compila/linka, so
nao foi medido nesta maquina" -- provado FALSO. O gap do shim C fica documentado na propria nota
do iOS: o que existe hoje, o que falta, e por que nao foi feito agora.
- `.jdi/phases/llm-mobile/PLAN.md`: T-8 vira `status: partial`, com o motivo explicito e o que
permanece entregue sem retrabalho.

O QUE NAO MUDA (fundacao de D-5/D-9 permanece; nada foi desfeito ou reescrito):
- `ILlamaNativeAccess` e `LlamaCppTranslationEngine` com os 15 testes NSubstitute (T-7) -- contrato
e loop de geracao provados no Core, TFM-agnostico, zero device/GGUF.
- `scripts/fetch-llama-xcframework.sh`, os pins (`LlamaCppRelease=b10453`,
`LlamaCppXcframeworkSha256`) e o `NativeReference Kind="Static" ForceLoad="True" IsCxx="True"` no
csproj -- cadeia de suprimento fail-closed provada (`--verify-only` aceita hash certo, rejeita
hash errado, ambos executados).
- `src/TranslateReader/Platforms/iOS/LlamaNativeAccess.cs` continua existindo com as 10
declaracoes -- documentado como INCOMPLETO, nunca removido, para nao perder o mapeamento de
assinatura ja feito nem o waiver de cobertura que ja o cobre.

CAMINHO PARA FECHAR (fora desta phase, decisao propria quando for feito):
(i) escrever e pinar um shim C compilado que exporte `tr_llama_*` sobre a API real `llama_*`
(provavelmente uma static lib propria, pinada como o XCFramework hoje), compilado e validado em
macOS; ou
(ii) redeclarar o P/Invoke direto contra as assinaturas `llama_*` reais -- exige marshalling de
structs (`llama_batch`, `llama_model_params`) e montar uma sampler chain; redesenho maior do
Access, tambem so validavel em macOS.
Nenhum dos dois entra nesta iteracao: sem macOS nao ha como compilar nem linkar para validar, e um
shim as cegas e exatamente o tipo de codigo que nao deve entrar sem prova (regra geral do processo,
nao apenas desta phase).

CUSTO ACEITO: iOS fica sem job de CI ate a camada de simbolos existir -- nenhuma regressao de
pipeline para ninguem (a alternativa, o job vermelho, quebraria toda PR, o que D-10 ja proibia). O
trabalho de T-7/T-8 que JA prova (contrato mockavel, loop de geracao, cadeia de suprimento
fail-closed) nao e jogado fora; so o rotulo de prontidao do binding nativo e corrigido para refletir
a realidade medida. Bloco 1 (Android) permanece a entrega completa e provada desta phase; Bloco 2
(iOS) fica registrado como fundacao pronta + gap conhecido e delimitado, para uma phase futura
fechar.
Loading
Loading