Skip to content
Open
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
30 changes: 28 additions & 2 deletions .agents/context/process-status.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,29 @@
"issue_doc": "docs/progress/ (F0-baseline, F0-contract, F{1,2,3}-baseline-contract, F0-graft) + 00-PHASES.md",
"github_issue": null,
"started": "2026-06-08T00:00:00Z",
"last_updated": "2026-08-20T15:53:16+09:00",
"last_updated": "2026-08-23T00:00:00+09:00",
"status": "in_progress",
"v022_thinking_separation_479": {
"issue": "https://github.com/nextain/naia-shell/issues/479",
"scope": "thinking을 최종 응답과 분리하고 모든 청크 경계에서 TTS 유입을 차단",
"requirements": "docs/requirements.md FR-CHAT-THINKING.1~2",
"scenario": "docs/user-scenarios.md UC-V022-THINKING-SEPARATION",
"status": "in_progress",
"p01_user_scenarios": "done",
"p02_test_scenarios": "done",
"p03_requirements": "done",
"p04_integration_test": "pending",
"p05_complete": "pending",
"last_updated": "2026-08-23T00:00:00+09:00"
},
"v022_tts_text_normalization_480": {
"issue": 480,
"P01_user_scenario": "done",
"P02_test_coverage_map": "done",
"P03_requirements": "done",
"P04_implementation_tests": "pending",
"P05_done_handoff": "pending"
},
"startup_update_prompt_468": {
"issue": "https://github.com/nextain/naia-shell/issues/468",
"scope": "앱 시작 시 명시적 동의가 필요한 업데이트 팝업과 버전별 30일 알림 유예, 기존 배너 및 설정 수동 확인 보존",
Expand Down Expand Up @@ -172,7 +193,12 @@
"sdlc_gates": {
"_note": "각 게이트의 최상위 status/deliverable/ref = 기본 트랙(current_work.issue = naia-shell-transplant, F0~F3) 소유. 병행 트랙은 같은 게이트의 tracks 에만 기재한다(한 사실 두 곳 금지 — 기본 트랙 상태를 tracks 에 복사하지 않는다).",
"P01": { "status": "done", "name": "user_scenarios", "deliverable": "docs/user-scenarios.md (UC1-14, S01~S71)", "ref": "완전성 13R 3연속 NONE",
"tracks": {
"tracks": {
"v022_chat_rich_markdown_483": {
"issue": 483,
"status": "implementation_validating",
"phases": { "P01": "done", "P02": "done", "P03": "done", "P04": "in_progress", "P05": "pending" }
},
"installer_crossplatform_377": { "status": "done", "deliverable": "docs/user-scenarios.md — 셸 feature 시나리오 표의 S-INSTALL 행 (#377, 2026-07-17)", "ref": "DONE 2026-07-17 — 적대 리뷰 R1~R8 (8라운드, 실측 근거 결함 전부 반영). P0 종료 조건이 '리뷰 2연속 클린' 에서 **'구현이 검증'** 으로 조정됨(루크 승인 2026-07-17, AI 임의 변경 아님): R5~R8 이 각각 직전 라운드의 개정에서 MAJOR 를 찾는 진자운동에 들어갔고 잔여 결함 계열(로그 정규식·줄 개수·glob 형태)은 P1 구현 + vitest golden + 실제 CI 가 더 싸고 확실하게 잡는다는 판단. 상세 = current_work.installer_crossplatform_377" },
"v020_signed_updater_recovery": { "status": "done", "deliverable": "docs/user-scenarios.md UC-V020-*", "ref": "Signed update, legacy feed, and failure-honesty journeys" }
}
Expand Down
26 changes: 26 additions & 0 deletions docs/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ localStorage `naia-config` 는 파일에서 하이드레이트되는 **순수
| **FR-INSTALL.4** | **Windows 설치 실측** — clean 상태에서 `tauri:build:bundle` → NSIS+MSI 산출 → **NSIS** 무인 설치(`/S`, currentUser, 관리자 불요) → **설치본 실행 파일로 기동 + 에이전트 핸드셰이크 확인**(e2e-tauri `TAURI_BINARY` env 오버라이드 신설 — 현행 debug 경로 하드코딩 해소). **판정은 FR-INSTALL.5 와 동형의 2조건(R6 개정)**: 핸드셰이크만으로는 *어느* node 로 떴는지 증명하지 못하는데, 하필 이 실측 머신은 정의상 빌드 머신이라 시스템 node 가 반드시 있어(빌드 전제조건) PATH 폴백이 성공하면 그대로 green 이 된다 — `node.exe` 동봉이 #377 의 출발점인데 Windows 번들 분기만 검증축 밖에 남는 셈. 따라서 ① 핸드셰이크 **AND** ② 설치본 로그(사용자 홈 아래 `.naia/logs/naia.log`)에서 `[Naia] node = ` 를 **포함하는 줄이 최소 2줄 AND 그 줄들이 전부** 설치본 resource_dir(= NSIS `$INSTDIR`) 하위일 것(**R8 — 개수 하한 필수**: 전칭은 0줄이면 공허참이라 그대로 두면 이 게이트가 조용히 green 이 되는데, Linux 와 달리 Windows 에는 그 공허참을 잡아 줄 mutation probe 가 없어 **여기가 Windows 번들 분기의 유일한 축**이다) — **FR-INSTALL.5 와 동일한 세션 스코프를 적용한다(R7 필수)**: 이 로그는 누적 파일이고 이 머신은 정의상 빌드 머신이라 `tauri dev`·e2e-tauri 가 남긴 시스템 node 줄이 이미 들어 있다 → 스코프 없이는 정상 설치본도 red. **마지막 `[Naia] === Session started ===` 이후 줄만** 판정한다. MSI 는 산출·실존 확인까지(WiX MSI = perMachine/관리자 승격이 표준 — per-user 무인 실측은 NSIS 담당). **순수 Windows(WSL 불요) 불변**: 빌드·설치·런타임 전 구간에 WSL/POSIX 셸 의존 금지 — 허용 외부 도구 = OS 기본 제공(curl·tar), tauri CLI 가 스스로 관리하는 번들러 도구(NSIS/WiX 자동 다운로드), **VS 부속 도구(vswhere — VS C++ 빌드도구가 이미 빌드 전제조건이라 신규 의존 아님)**까지 | S-INSTALL | 실 빌드 산출물(.exe/.msi 실존+크기) · NSIS 무인 설치 후 설치본 기동 스모크 = **기동+핸드셰이크 AND `[Naia] node = ` 포함 줄이 최소 2줄 AND 전부 `$INSTDIR` 하위**(R6/R8 — Windows 번들 분기 실행 증명. 판정 범위 = 마지막 `=== Session started ===` 포함 줄 이후. 그 외 dev 환경 가정 오염 금지) |
| **FR-INSTALL.5** | **CI 3 OS 빌드+설치 증명** — `.github/workflows/build-installers.yml`: windows/ubuntu/macos-latest 매트릭스, **push/수동 트리거만**(fork PR 제외 — 보안), naia-agent(공개) sibling clone, cascade-loader 는 optional 경로(private repo — 시크릿 없이 skip+로그), 산출물 artifact 업로드. **ubuntu job 은 빌드에 더해 deb 설치 → xvfb 기동 스모크**(번들 node 실사용 증명 — linux 미실측 공백 해소). **스모크 성공 판정(R5 개정 — 2조건 AND)**: 설치본 바이너리를 xvfb 아래 기동 → **120초 내** 셸 로그(사용자 홈 아래 `.naia/logs/naia.log`)에 ① 마커 `[Naia] agent-core gRPC @` 출현(gRPC 준비 핸드셰이크 — 자식 stdout 의 `GRPC_LISTENING` 을 실제 수신한 **뒤에만** 방출되므로 node 스폰 실패 시 나올 수 없음) **AND** ② `[Naia] node = ` 를 **포함하는 줄이 최소 2줄**(FR-INSTALL.3 의 부팅당 2줄 — agent·BGM) **AND** 그 줄들의 경로가 **전부 설치본 resource_dir 하위** = green. **개수 하한이 AND 로 붙는 이유(R8)**: 전칭("전부 … 하위")은 대상이 0줄이면 **참**이라, 로그 접두사 드리프트나 폴백 클로저 안쪽 기록 같은 이유로 줄이 사라지면 ② 가 공허참으로 green 이 되고 ①(핸드셰이크)만 남는데 그건 R6 가 폐기한 상태 그대로다 — 하한을 못 박으면 공허참이 원리적으로 불가능해진다. **"정확히" 가 아니라 "최소" 인 이유(R8 자체 교정)**: `restart_agent` 가 `spawn_agent_core` 를 재호출하면 node 를 다시 해석해 **3번째 줄**이 나온다(실측) — 상한을 박으면 재시작이 일어난 실행에서 정상 설치본이 거짓 red 가 된다. 하한 + 전칭이면 공허참도 부분 누출도 잡으면서 오탐이 없다. 하나라도 불충족·프로세스 조기 종료 = red(**R6**: "하나라도 하위" 가 아니라 **전부** — 그래야 BGM 만 시스템 node 로 새어도 red 가 된다). **판정 범위 = 이번 부팅의 줄만(R7 필수 — 세션 스코프)**: `naia.log` 는 `append` 전용 **머신 단위 누적 파일**이라(절단·회전 코드 0건) 이전 부팅들의 시스템 node 줄이 그대로 남아 있다 — 스코프가 없으면 ② 는 **정상 설치본에서도 영구히 red** 이고, 같은 job 안에서 도는 mutation probe 가 스모크보다 먼저 실행되면 그 줄이 섞여 **실행 순서에 판정이 좌우**된다. 따라서 **파일의 마지막 `[Naia] === Session started ===` 이후 줄만** 대상으로 한다(이 구분자는 setup 최상단에서 방출되어 두 spawn 보다 항상 선행 — 실측). 로그 파일 삭제로 대신하지 않는다(빌드 머신의 사용자 로그를 파괴하므로). 창 생존만으론 번들 node 를 증명하지 못하므로(에이전트 스폰 실패해도 창은 뜸) 로그 관측 기준이며, `NAIA_MINIMAL` 미설정 기본 부팅에서 스폰이 무조건 일어나는 것이 전제(CI job env 에 해당 변수 부재). **"PATH 에서 node 제거" 에 의존하지 않는다(R5 — 구설계 폐기)**: unix 폴백은 PATH 와 무관하게 사용자 홈 아래 `.nvm/versions/node` 를 **직접 디렉토리 스캔**하므로 PATH 만 끊는 것은 폴백을 차단하지 못한다. 현 GitHub 러너 이미지는 그 디렉토리가 비어 있어(`nvm alias default system`) 우연히 통과하지만, 그러면 **판정력이 외부 러너 이미지에 위탁**되어 이미지가 바뀌는 날 조용히 무력화된다. 더구나 FR-INSTALL.3 이 번들 node 를 **최우선**으로 두므로 PATH 제거는 정상 경로에 애초에 아무 영향이 없다 — 그래서 관측 대상을 ②(실제 사용된 경로)로 바꾼다. **자기 검증(mutation probe)**: 번들 node 를 일부러 제거한 실행 1회가 **red 가 되는지** CI 에서 확인 — 폴백이 하나 늘어도 게이트가 조용히 통과하지 않음을 증명(FR-INSTALL.6 의 부정 케이스 정신과 동일). **mac = arm64 전용·미서명·미공증 정직 표기**(`macos-latest` = arm64 러너 → `process.arch`=arm64 → darwin-arm64 node + arm64 호스트 타깃 = **Apple Silicon 전용 산출물, Intel Mac 몫 없음**. Intel 은 `macos-*-intel` 라벨 추가가 필요하며 후속. 우클릭 열기 필요) — 서명/updater `.sig` 는 범위 밖(별도 결정) | S-INSTALL | CI 3 job 전부 green + artifact 실존 · ubuntu 설치+기동 스모크 green(마커 **AND** node 줄 최소 2줄 **AND** 그 경로가 전부 resource_dir 하위 — 세션 스코프 적용) + mutation probe red 확인 (mac 완료선 = **arm64** 빌드 성공, 실기기 설치는 미보유 정직 표기) |
| **FR-INSTALL.6** | **산출물 검증 스크립트 1개** `scripts/verify-artifacts.mjs`(3 OS 공통, OS 분기=매트릭스 — **R5: 경로·파일명 확정**, 미지정 시 구현자 분기): 매트릭스 OS 행의 **`artifacts`(FR-INSTALL.1 — glob + minBytes)** 를 읽어 ① 각 glob 이 **정확히 1개 이상** 매치 ② 매치된 파일이 `minBytes` 이상 ③ SHA256 을 **stdout + `artifacts.sha256` 파일**로 기록(CI 는 이 파일을 artifact 로 함께 업로드 — 현 범위의 소비자는 사람의 사후 대조이며, 자동 비교는 하지 않음을 명시). `minBytes` 가 null 이면 **명확한 에러로 중단**(임계 미정 = red, 조용한 통과 금지). CI 각 job 말미 + 빌드 머신에서 동일 실행. **판정 로직은 주입 가능한 순수 함수로 분리(R8 — FR-INSTALL.2 의 conf 생성기와 같은 제약)**: `verifyArtifacts({ bundleDir, artifacts })` 로 번들 루트와 매트릭스 행을 **인자로 받고**, CLI 진입점은 이를 호출만 한다. 이 저장소의 동류 스크립트는 경로를 `import.meta.url` 기준으로 자기 고정하는 관례라(`check-build-contract.mjs` 선례), 그대로 두면 부정 케이스 테스트가 실 번들을 건드리거나 **판정 로직을 재구현**하는 수밖에 없다 — 재구현하면 실제 스크립트가 glob 0 매치를 건너뛰거나 크기 비교 부호를 뒤집어도 테스트는 **영원히 green** 이라 이 FR 의 목적("항상 통과 스크립트" 차단)이 정확히 무력화된다. 부정 테스트는 **이 함수를 import** 해 임시 디렉토리 픽스처(빈 디렉토리 / minBytes 미만 파일)로 구동한다 — 재구현 금지. **자기 검증 포함**: 부정(negative) 케이스 단위 테스트(산출물 부재/과소 크기 → red)로 "항상 통과 스크립트" 차단 | S-INSTALL | 검증 스크립트 실행(Windows 빌드 머신 + CI 3 OS) + **부정 케이스 단위 테스트** `scripts/__tests__/verify-artifacts.test.ts`(부재·과소 크기→red. 경로 근거 = FR-INSTALL.2 의 `src-tauri/**` vitest 미수집 실측) |
| **FR-CLI.1** | 설치 표면의 `naia` 명령은 기존 `naia-shell` 실행 파일을 가리키는 얇은 alias여야 하며 실행 파일·업데이터 식별자는 개명하지 않는다. gateway 모니터 명령은 별도 이름으로 이관한다. | UC-CLI-OPEN | 3 OS 설치 산출물 alias/PATH 검사와 gateway 참조 전수 검사 |
| **FR-CLI.2** | `naia <file>`은 호출 cwd 기준 상대 경로와 절대 경로를 정규화하고, 실존하는 일반 파일만 `workspace-open-file-request`로 전달한다. 실행 중인 셸은 포커스 후 열고, 콜드 스타트는 UI 준비 뒤 연다. | UC-CLI-OPEN | Rust 단위 + 네이티브 실행 중/콜드 스타트 인수 테스트 |
| **FR-CLI.3** | CLI 파일 열기는 기존 workspace `openFile` API를 재사용하며 파일 내용이나 경로를 로그·URL에 복제하지 않는다. 잘못된 인자는 셸 기동을 막지 않는다. | UC-CLI-OPEN | 프론트 이벤트 결선 테스트 + 오류/디렉터리/미존재 경로 부정 테스트 |

> NFR: **NFR-noWSL(불변)** — 빌드·설치·런타임 어느 구간에도 WSL 요구 금지(현행 0건을 요구사항으로 고정). · NFR-honesty — 미실측(mac 실기기)·미서명을 문서와 산출물 설명에 그대로 표기, "지원" 위장 금지. · 재현성 = "사람 기억에 의존하는 수동 단계 0". ⚠️ 범위 밖(별도 이슈로 후속): 코드 서명(win 인증서·mac 공증), updater `.sig` 생성/키, **updater endpoint stale**(base conf 가 폐기된 `nextain/naia-os` releases 를 가리킴 — 설치본 첫 실행 시 죽은 endpoint 조회, 후속 이슈로 교정), flatpak 경로, `WslSetupScreen` 죽은 레거시 삭제(기존 DEFER 유지).

Expand Down Expand Up @@ -821,3 +824,26 @@ unrelated direct TRT service on port 8910.
| **FR-WORKSPACE-MARKDOWN.1** | Selecting `.md` or `.markdown` in FileTree opens a GFM preview by default and retains the existing document-tab lifecycle. The user can switch between preview and source, and an oversized or unreadable document shows a bounded, retryable error instead of blocking the shell. | viewer component tests + Linux Chromium FileTree journey |
| **FR-WORKSPACE-MARKDOWN.2** | Relative links and images resolve from the current document or Workspace root. Canonical file reads remain enforced by the Rust Workspace boundary; traversal, raw HTML, script, protocol-relative content, and dangerous URL schemes cannot execute or read outside the Workspace. HTTP(S) links use the explicit system opener. | resolver/security/image tests + Rust boundary tests |
| **FR-WORKSPACE-MARKDOWN.3** | The preview exposes an article landmark, keyboard focus indication, semantic GFM tables/tasks/headings, descriptive external-link text, and honest missing-image status while preserving code, image, PDF, CSV, and log viewers. | accessibility component assertions + existing viewer regression suite |

## v0.2.2 thinking/final response separation (#479)

| ID | Normative requirement | Verification |
|---|---|---|
| **FR-CHAT-THINKING.1** | Structured thinking events and case-insensitive `<think>…</think>` text tags are accumulated only in the message `thinking` field. Tag tokens may be split at any stream boundary; neither tokens nor enclosed text may enter visible final content, the sentence chunker, or the TTS pipeline. Text following the closing tag remains complete and ordered. | incremental filter unit tests over every tag boundary + ChatArea TTS integration regression |
| **FR-CHAT-THINKING.2** | Streaming and persisted thinking use a separate semantic disclosure that is closed by default. This display rule never changes the unconditional exclusion of thinking from speech. | ChatArea component assertions for streaming/persisted closed disclosure, final answer rendering, and keyboard-accessible summary |
## v0.2.2 LLM→TTS 발화 텍스트 정규화 단일화 (#480)

- `FR-VOICE-TEXT.1`: 모든 TTS provider 경로는 `text-filter`의 공통 규칙과 locale별 확장 규칙을 거친 동일한 발화 문자열만 사용해야 한다.
- `FR-VOICE-TEXT.2`: 필터는 Markdown 표식만 제거해 본문을 보존하고, fenced code/Mermaid, URL, 제어 태그, 이모지·이모티콘과 장식 문자를 제거해야 한다.
- `FR-VOICE-TEXT.3`: inline code는 locale 레지스트리와 기본 fallback으로 처리하고, 정규화 결과가 비면 발화 요청을 생성하지 않아야 한다.
### FR-CHAT-MARKDOWN.1 — 안전한 채팅 Markdown

assistant 채팅은 GFM을 렌더링하되 원시 HTML 및 스크립트를 실행하지 않아야 한다.

### FR-CHAT-MARKDOWN.2 — 코드와 Mermaid

fenced code는 언어·복사·접기·워크스페이스 전환을 제공하고, Mermaid는 워크스페이스와 공유하는 strict 렌더러를 사용하며 실패 시 원문을 보존해야 한다.

### FR-PERMISSION-SHORTCUT.1 — 권한 팝업 공통 단축키 (#477)

도구 권한 팝업은 실행 `Alt+Y`, 항상 실행 `Alt+A`, 취소 `Alt+N`을 하나의 공통 정의에서 표시·해석하고, macOS에서는 같은 Alt 키를 Option 기호로 표시한다. 팝업이 열린 동안에만 수정키가 정확히 일치하는 최초 keydown을 한 번 처리한다.
Loading
Loading