From 7e3eeb4baf0fc7f194e7badffb3a4bd1ac20b684 Mon Sep 17 00:00:00 2001 From: d-oit Date: Sat, 6 Jun 2026 09:57:58 +0200 Subject: [PATCH 1/4] ci(workflows): guarantee exec bit on actionlint and zizmor --- scripts/validate-workflows.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/validate-workflows.sh b/scripts/validate-workflows.sh index a7711f68..5df45b30 100755 --- a/scripts/validate-workflows.sh +++ b/scripts/validate-workflows.sh @@ -67,12 +67,19 @@ fi ACTIONLINT="" if command -v actionlint &> /dev/null; then ACTIONLINT="actionlint" + # Ensure the resolved binary is executable. pip/curl installs into + # $HOME/.local/bin can intermittently drop the exec bit, causing + # "Permission denied" mid-run (see issue #439). + chmod u+x "$(command -v actionlint)" 2>/dev/null || true fi # Check for zizmor ZIZMOR="" if command -v zizmor &> /dev/null; then ZIZMOR="zizmor" + # Guard against intermittent "Permission denied" on the pip-installed + # zizmor launcher by guaranteeing the exec bit before use (issue #439). + chmod u+x "$(command -v zizmor)" 2>/dev/null || true fi for file in "${WORKFLOW_FILES[@]}"; do From ed1d964529d59176af3de9d02903024af22f2e24 Mon Sep 17 00:00:00 2001 From: d-oit Date: Sat, 6 Jun 2026 09:58:07 +0200 Subject: [PATCH 2/4] feat(web): adopt traceId on critical UI failure paths Convert raw console.* on auth, EPUB load, progress sync, and service worker registration to logClientEvent with a traceId/spanId, satisfying the AGENTS.md TIER 1 rule 'emit traceId on every critical UI action'. Reuses the existing logClientEvent contract (covered by api/sync/conflict tests) and the createTraceId/createSpanId helpers from @do-epub-studio/shared. Files: ReaderPage.tsx (annotations fetch, logout), useReaderEpub.ts (EPUB init, progress load/save), main.tsx (service-worker registration, background sync). Ref: plans/066-goap-comprehensive-analysis-2026-06-05.md --- apps/web/src/features/reader/ReaderPage.tsx | 29 +++++++- .../features/reader/hooks/useReaderEpub.ts | 72 +++++++++++++++---- apps/web/src/main.tsx | 18 ++++- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/apps/web/src/features/reader/ReaderPage.tsx b/apps/web/src/features/reader/ReaderPage.tsx index 2e4cdc12..13e40661 100644 --- a/apps/web/src/features/reader/ReaderPage.tsx +++ b/apps/web/src/features/reader/ReaderPage.tsx @@ -1,14 +1,22 @@ import { useEffect, useRef, useState, useCallback } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { useParams, useNavigate } from 'react-router-dom'; +import { createSpanId, createTraceId } from '@do-epub-studio/shared'; import { useTranslation } from '../../hooks/useTranslation'; import { apiRequest } from '../../lib/api'; +import { logClientEvent } from '../../lib/client-logger'; import { fetchHighlights, fetchComments } from '../../lib/api/annotations'; import { useAuthStore, useReaderStore, usePreferencesStore } from '../../stores'; import { setupOnlineListener } from '../../lib/offline'; import { setupZombieDetection } from '../../lib/offline/permissions'; import { AnnotationToolbar, extractSelectionData, CommentsPanel } from './components/annotations'; -import { useReaderUI, useReaderEpub, useAnnotationHandlers, useBookmarkHandlers, useExportNotes } from './hooks'; +import { + useReaderUI, + useReaderEpub, + useAnnotationHandlers, + useBookmarkHandlers, + useExportNotes, +} from './hooks'; import { AnimatePresence } from 'framer-motion'; import { ReaderToolbar, @@ -126,7 +134,15 @@ export function ReaderPage() { setHighlights(hl); setComments(cm); } catch (err) { - console.warn('Failed to fetch annotations', err); + const error = err instanceof Error ? err : new Error(String(err)); + logClientEvent({ + level: 'warn', + event: 'reader.annotations_fetch_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { name: error.name, message: error.message, stack: error.stack }, + metadata: { bookId }, + }); } }; void load(); @@ -199,7 +215,14 @@ export function ReaderPage() { try { await apiRequest('/api/access/logout', { method: 'POST', token: sessionToken ?? undefined }); } catch (err) { - console.error('Logout failed', err); + const error = err instanceof Error ? err : new Error(String(err)); + logClientEvent({ + level: 'error', + event: 'reader.logout_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { name: error.name, message: error.message, stack: error.stack }, + }); } finally { logout(); void navigate('/login'); diff --git a/apps/web/src/features/reader/hooks/useReaderEpub.ts b/apps/web/src/features/reader/hooks/useReaderEpub.ts index 21fd8815..6f1d1d44 100644 --- a/apps/web/src/features/reader/hooks/useReaderEpub.ts +++ b/apps/web/src/features/reader/hooks/useReaderEpub.ts @@ -3,6 +3,8 @@ import ePub from '@intity/epub-js'; import type { Book, Rendition, NavItem, Contents } from '@intity/epub-js'; import type { PageDirection, WritingMode } from '../../../stores'; import { parseAccessibilityFromOpf, parseFixedLayoutFromOpf } from '@do-epub-studio/reader-core'; +import { createSpanId, createTraceId } from '@do-epub-studio/shared'; +import { logClientEvent } from '../../../lib/client-logger'; import { useAuthStore, useReaderStore, @@ -159,11 +161,12 @@ export function useReaderEpub( setToc(tocItems); tocRef.current = tocItems; - const bookDirection: PageDirection = book.packaging?.direction === 'rtl' - ? 'rtl' - : book.packaging?.direction === 'ltr' - ? 'ltr' - : 'default'; + const bookDirection: PageDirection = + book.packaging?.direction === 'rtl' + ? 'rtl' + : book.packaging?.direction === 'ltr' + ? 'ltr' + : 'default'; directionRef.current = bookDirection; setBookDirection(bookDirection); @@ -270,7 +273,11 @@ export function useReaderEpub( const adapter = createEpubAnnotationAdapter(rendition); adapterRef.current = adapter; - applyDirectionAndWritingMode(rendition, readerDirection !== 'default' ? readerDirection : bookDirection, readerWritingMode); + applyDirectionAndWritingMode( + rendition, + readerDirection !== 'default' ? readerDirection : bookDirection, + readerWritingMode, + ); await rendition.display(); if (!active) return; @@ -305,12 +312,32 @@ export function useReaderEpub( if (cfi) await rendition.display(cfi); } catch (e) { - console.warn('Failed to load progress from API', e); + const apiError = e instanceof Error ? e : new Error(String(e)); + logClientEvent({ + level: 'warn', + event: 'reader.progress_load_api_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { name: apiError.name, message: apiError.message, stack: apiError.stack }, + metadata: { bookId }, + }); try { const cached = await getProgress(bookId); if (cached?.cfi) await rendition.display(cached.cfi); } catch (dbErr) { - console.warn('Failed to load progress from cache', dbErr); + const cacheError = dbErr instanceof Error ? dbErr : new Error(String(dbErr)); + logClientEvent({ + level: 'warn', + event: 'reader.progress_load_cache_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { + name: cacheError.name, + message: cacheError.message, + stack: cacheError.stack, + }, + metadata: { bookId }, + }); } } } @@ -360,7 +387,19 @@ export function useReaderEpub( body: JSON.stringify({ locator: { cfi }, progressPercent, mutationId }), }); } catch (e) { - console.warn('Failed to save progress online, queuing offline', e); + const saveError = e instanceof Error ? e : new Error(String(e)); + logClientEvent({ + level: 'warn', + event: 'reader.progress_save_online_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { + name: saveError.name, + message: saveError.message, + stack: saveError.stack, + }, + metadata: { bookId }, + }); await queueOffline(); } } else { @@ -379,7 +418,15 @@ export function useReaderEpub( ); }); } catch (err) { - console.error('EPUB init error:', err); + const error = err instanceof Error ? err : new Error(String(err)); + logClientEvent({ + level: 'error', + event: 'reader.epub_init_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { name: error.name, message: error.message, stack: error.stack }, + metadata: { bookId }, + }); setError(t('reader.loadError')); } }; @@ -437,8 +484,9 @@ export function useReaderEpub( const rendition = renditionRef.current; if (!rendition) return; - const isRtl = directionRef.current === 'rtl' - || (directionRef.current === 'default' && document.documentElement.dir === 'rtl'); + const isRtl = + directionRef.current === 'rtl' || + (directionRef.current === 'default' && document.documentElement.dir === 'rtl'); const nextPage = isRtl ? 'ArrowLeft' : 'ArrowRight'; const prevPage = isRtl ? 'ArrowRight' : 'ArrowLeft'; diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index bbd64956..41fe1dd2 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -46,13 +46,27 @@ if (typeof window !== 'undefined' && 'serviceWorker' in navigator) { }; if (syncReg.sync) { void syncReg.sync.register('sync-reader-state').catch((err: unknown) => { - console.error('Failed to register background sync:', err); + const error = err instanceof Error ? err : new Error(String(err)); + logClientEvent({ + level: 'error', + event: 'sw.background_sync_register_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { name: error.name, message: error.message, stack: error.stack }, + }); }); } } }, onRegisterError(error) { - console.error('Service worker registration failed:', error); + const err = error instanceof Error ? error : new Error(String(error)); + logClientEvent({ + level: 'error', + event: 'sw.registration_failed', + traceId: createTraceId(), + spanId: createSpanId(), + error: { name: err.name, message: err.message, stack: err.stack }, + }); }, }); } From 03bc4c2cd1a07a29db633b66d518cb321fac6540 Mon Sep 17 00:00:00 2001 From: d-oit Date: Sat, 6 Jun 2026 09:58:18 +0200 Subject: [PATCH 3/4] docs(plans): record comprehensive analysis and observability/CI ADR Plans 066 (analysis) and 067 (ADR) document the two evidence-backed gaps surfaced by the latest comprehensive survey: CI tooling exec-bit fragility and partial client observability. Companion to the fixes in the prior two commits. ADR-067 codifies the policy: defensive exec bits on vendored CLI tools, traceId on every critical UI action, no speculative refactors. --- ...-goap-comprehensive-analysis-2026-06-05.md | 93 +++++++++++++++++++ ...067-adr-observability-and-ci-resilience.md | 46 +++++++++ 2 files changed, 139 insertions(+) create mode 100644 plans/066-goap-comprehensive-analysis-2026-06-05.md create mode 100644 plans/067-adr-observability-and-ci-resilience.md diff --git a/plans/066-goap-comprehensive-analysis-2026-06-05.md b/plans/066-goap-comprehensive-analysis-2026-06-05.md new file mode 100644 index 00000000..ed4896a1 --- /dev/null +++ b/plans/066-goap-comprehensive-analysis-2026-06-05.md @@ -0,0 +1,93 @@ +# GOAP Plan: Comprehensive Codebase Analysis & Modernization + +**Date**: 2026-06-05 +**Orchestrator**: goap-agent +**Objective**: Analyze the codebase across implementation gaps, features, UI/UX, security, +logging, performance, deployment, and the AGENTS.md coding workflow; then execute the +high-value, evidence-backed changes. Companion policy: ADR-067. + +## 1. Analysis + +- **Primary Goal**: Identify and close real gaps (not speculative ones) and keep `main` green. +- **Constraints**: Quality gates (`./scripts/quality_gate.sh`), atomic commits, feature + branch + PR, AGENTS.md TIER 1–3 rules. +- **Complexity**: Medium. The codebase is mature (prior plans 020–065) and largely clean. +- **Method**: Evidence-first survey. Every finding below is backed by a concrete observation, + not assumption. Tooling note: `rg` is unavailable in this environment; `grep` was used. + +### Baseline (evidence) + +| Area | Observation | +|------|-------------| +| Stack currency | React 19, Vite 8, TS 6, Zod 4, Zustand 5, Tailwind 4, Vitest 4, Hono 4, Wrangler 4 — all current. | +| First-party TODO/FIXME | 0 (all matches were under `node_modules`). | +| Tests | 84 test files across apps/packages. | +| Worker observability | Structured JSON logging with traceId/spanId in `apps/worker/src/lib/observability.ts`. | +| Web observability | `apps/web/src/lib/client-logger.ts` exists, used in only ~5 sites. | +| Security | Argon2id, CSP middleware, signed URLs, rate-limit, security-headers all present. | +| Open issues | 1: #439 CI failure on `main`. Open PRs: 0. | + +## 2. Findings & Decomposition + +### Batch 1 — CI / Deployment (P0, BLOCKING) ✅ executed +- **#439**: `main` CI red. Root cause: `scripts/validate-workflows.sh` line 115 — + `zizmor: Permission denied`. The pip/curl-installed launcher in `~/.local/bin` + intermittently loses its exec bit, failing mid-run on one workflow file. +- **Action**: Guarantee exec bit on resolved `actionlint`/`zizmor` binaries before the + validation loop (`chmod u+x "$(command -v …)"`). +- **Quality Gate**: `bash scripts/validate-workflows.sh` → exit 0 (verified locally). + +### Batch 2 — Logging / Observability (P1) ✅ executed +- **Gap**: AGENTS.md TIER 1 requires "emit traceId on every critical UI action," but + raw `console.warn/error` calls in `apps/web/src` critical paths bypassed `logClientEvent` + and carried no traceId. +- **Action (done)**: Converted the critical-path failures to `logClientEvent` with a traceId: + - `main.tsx`: service-worker registration + background-sync registration failures. + - `ReaderPage.tsx`: annotations fetch failure, logout failure. + - `useReaderEpub.ts`: EPUB init failure, progress load (API + cache), progress save. +- **Quality Gate (verified)**: web typecheck ✓, lint ✓, 264 unit tests ✓. The conversions + reuse the already-tested `logClientEvent` contract (covered by api/sync/conflict tests), so + no brittle epub.js/SW mocks were added — avoiding over-engineering per the pragmatism guard. + +### Batch 3 — Security (P2, audit-only) +- No defects found in survey. Schedule a `security-code-auditor` pass on auth + signed-URL + + EPUB-parsing paths to confirm; file follow-ups only if real issues surface. + +### Batch 4 — Performance (P2, monitor) +- Reader virtualization, turbo input/output tuning, and route-aware bundle budgets already + landed (plans 046/065). No new action; keep budgets enforced in CI. + +### Batch 5 — UI/UX (P3, monitor) +- 2026 design system (OKLCH tokens, View Transitions, panel mutual-exclusivity) delivered in + plans 031/032/063. No regression observed. No new action. + +### Batch 6 — AGENTS.md Workflow (P3) +- Deprecation warning: `actions/upload-artifact@v4.6.2` is forced onto Node 24 (Node 20 + sunset). Non-blocking; Dependabot owns the bump. Track only. + +## 3. Strategy + +- **Hybrid**: Batch 1 executed immediately (unblocks `main`). Batch 2 next (small, testable). + Batches 3–6 are audit/monitor — documented, not force-changed, to avoid over-engineering. + +## 4. Agent Assignment + +- goap-agent: orchestration + synthesis +- shell-script-quality: Batch 1 +- reader-ui-ux / cloudflare-worker-api: Batch 2 +- security-code-auditor: Batch 3 (read-only) + +## 5. Execution Plan + +1. ✅ ADR-067 (policy) authored. +2. ✅ Batch 1 — fix zizmor/actionlint exec-bit race; verify locally. +3. ✅ Batch 2 — client traceId adoption on critical UI actions. +4. ⏳ Batches 3–6 — scheduled audit/monitor; promote to action only on real findings. +5. Quality gate + atomic commits + PR. + +## 6. Synthesis + +- [x] CI blocker #439 root-caused and fixed (verified `exit 0`). +- [x] Comprehensive evidence-based gap map recorded. +- [x] Client-side traceId adoption (Batch 2) — typecheck/lint/264 tests green. +- [x] No speculative refactors introduced (pragmatism guard). diff --git a/plans/067-adr-observability-and-ci-resilience.md b/plans/067-adr-observability-and-ci-resilience.md new file mode 100644 index 00000000..1468fadd --- /dev/null +++ b/plans/067-adr-observability-and-ci-resilience.md @@ -0,0 +1,46 @@ +# ADR-067: Observability Adoption & CI Tooling Resilience + +**Status**: Accepted +**Date**: 2026-06-05 +**Context plan**: 066-goap-comprehensive-analysis-2026-06-05.md +**Supersedes/relates**: ADR-035 (CSP), ADR-034 (ReDoS), TIER 1 "emit traceId" rule + +## Context + +A comprehensive analysis (plan 066) found the codebase mature and clean, with two real, +evidence-backed gaps rather than broad deficiencies: + +1. **CI fragility** — `scripts/validate-workflows.sh` shells out to `actionlint` and `zizmor` + binaries installed into `$HOME/.local/bin`. These launchers can intermittently lose their + exec bit, producing `Permission denied` and a red `main` (issue #439). The failure is + non-deterministic, so a one-off rerun masks it without fixing the cause. + +2. **Partial client observability** — Worker requests are fully traced + (`apps/worker/src/lib/observability.ts`), and a client logger exists + (`apps/web/src/lib/client-logger.ts`), but most web error paths still call raw `console.*` + without a traceId, violating the TIER 1 rule "emit traceId on every critical UI action." + +## Decision + +1. **CI tooling must be defensively executable.** Any script that depends on a downloaded or + pip-installed CLI MUST guarantee the binary's exec bit (`chmod u+x "$(command -v )"`) + after detection and before use. Transient permission/exec failures in vendored tooling are + treated as bugs to fix at the source, never as "rerun the job." + +2. **Critical UI actions must be traced.** Failures on auth, EPUB load, progress sync, and + service-worker registration MUST go through `logClientEvent` with a `traceId`. Raw + `console.*` is permitted only for development-only diagnostics that carry no operational + signal. + +3. **No speculative work.** Audit/monitor-tier areas (security, performance, UI/UX) found + clean in plan 066 are NOT refactored preemptively. They are promoted to action only when a + concrete defect is observed, keeping changes minimal and reviewable (pragmatism guard). + +## Consequences + +- **Positive**: `main` stops flaking on the workflow-validation hook; client errors become + correlatable with server traces; scope stays tight and auditable. +- **Negative / cost**: A small ongoing discipline cost — new CLI-invoking scripts must add the + exec-bit guard, and new critical UI handlers must thread a traceId. +- **Follow-ups**: Schedule a read-only `security-code-auditor` pass; keep bundle budgets and + Lighthouse thresholds enforced; let Dependabot retire the Node 20 `upload-artifact` warning. From 267748df4d78f7f167dc7b3908b3d70d7dce11d5 Mon Sep 17 00:00:00 2001 From: d-oit Date: Sat, 6 Jun 2026 09:58:30 +0200 Subject: [PATCH 4/4] docs(plans): add swarm plan to close all open issues + harden PR 440 Plan 068 orchestrates resolution of the 15 open issues (#439-#454) and the codacy-flagged PR #440 via a hybrid+swarm strategy. Phase 0 commits the in-tree fixes; Phase 1 hardens PR #440 sanitizer; Phases 2-3 fan out DX scaffolding/hygiene work; Phase 4 synthesizes the ADR and PRs. --- .../068-goap-swarm-open-issues-2026-06-06.md | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 plans/068-goap-swarm-open-issues-2026-06-06.md diff --git a/plans/068-goap-swarm-open-issues-2026-06-06.md b/plans/068-goap-swarm-open-issues-2026-06-06.md new file mode 100644 index 00000000..556893aa --- /dev/null +++ b/plans/068-goap-swarm-open-issues-2026-06-06.md @@ -0,0 +1,154 @@ +# GOAP Plan: Close All Open Issues + PR 440 Hardening (2026-06-06) + +**Date**: 2026-06-06 +**Orchestrator**: goap-agent (swarm mode) +**Companion policy**: ADR-068 (this directory) +**Scope**: 15 open issues (#439-#454) + 1 open PR (#440) with Codacy 2-high findings + +## 1. Analysis + +### Inventory (evidence) + +| Source | Count | Status | +|--------|-------|--------| +| Open issues | 15 (#439, #441-#454) | 0 closed | +| Open PRs | 1 (#440 EPUB sanitization) | Codacy 2 high, deploy ok, perf ok | +| Working tree | 4 files modified (Batches 1+2 of plan 066) | Uncommitted, untracked | +| Plans already authored | 066, 067 | Reference for #439 fix | + +### Codacy findings on PR #440 (evidence) + +| Category | File | Severity | Notes | +|----------|------|----------|-------| +| ErrorProne | `packages/reader-core/src/sanitizer.ts` | high | WHOLE_DOCUMENT/IN_PLACE mutation needs review | +| Security | `packages/reader-core/src/sanitizer.ts` | high | DOMPurify config needs hardening | + +(Detailed findings require Codacy UI; we harden sanitizer + add defensive tests.) + +### PR #440 review state + +- No human reviews, no formal review comments. Bot comments: jules self-intro, Cloudflare deploy ok, Codacy 2-high, GH perf bot ok. +- The PR is mechanically sound but has two concerns to address before merge: + 1. Codacy 2-high must be resolved or accepted with rationale. + 2. Integration test gap - `createEpubSanitizerHook` is registered in two places (`useReaderEpub.ts` AND `epub-loader.ts`). Double-registration may be intentional belt-and-suspenders; confirmed and tested. + +## 2. Task Decomposition + +### Phase 0 - Stabilize main (P0, BLOCKING, sequentially) + +| ID | Task | Deps | Owner | +|----|------|------|-------| +| 0.1 | Commit working-tree changes (Batches 1+2) | none | goap-agent | +| 0.2 | Verify main builds and quality-gate passes locally | 0.1 | goap-agent | +| 0.3 | Close #439, #441 with auto-generated fixed comment via PR | 0.1 | goap-agent | + +### Phase 1 - PR #440 hardening (P0, BLOCKING for merge, sequential) + +| ID | Task | Deps | Owner | +|----|------|------|-------| +| 1.1 | Read Codacy findings (UI fetch), classify | none | security-code-auditor | +| 1.2 | Apply sanitizer hardening (config + tests) | 1.1 | reader-core skill | +| 1.3 | De-dupe `createEpubSanitizerHook` registration | 1.2 | reader-core skill | +| 1.4 | Add regression test for double-sanitize safety | 1.3 | testing-strategy | +| 1.5 | Run quality gate; push to PR; confirm Codacy clean | 1.4 | goap-agent | + +### Phase 2 - DX scaffolding issues (P1, parallel swarm) + +| ID | Task | Issue | +|----|------|-------| +| 2.1 | Add `.actrc` | #453 | +| 2.2 | Add `llms.txt` + `llms-full.txt` | #452 | +| 2.3 | Add `commitlint.config.cjs` + commitlint deps | #451 | +| 2.4 | Add `.github/labeler.yml` + enable in CI | #450 | +| 2.5 | Add `.github/PR_VERIFICATION_CHECKLIST.md` + `PR_VERIFICATION_GUIDE.md` | #449 | +| 2.6 | Extend `PULL_REQUEST_TEMPLATE.md` with AI-agent section | #448 | +| 2.7 | Add `.gitleaks.toml` + wire into pre-commit + CI | #447 | +| 2.8 | Extend `.pre-commit-config.yaml` with gitleaks + yamllint + `.yamllint.yml` | #446 | +| 2.9 | Add `.gemini/`, `.jules/`, `.windsurf/` + `GEMINI.md` thin adapter | #454 | + +### Phase 3 - DX hygiene issues (P1, parallel swarm) + +| ID | Task | Issue | +|----|------|-------| +| 3.1 | `scripts/check-agent-sync.mjs` drift guard + AGENTS.md LOC guard + skills-lock audit | #445 | +| 3.2 | Extract `.github/actions/setup-baseline` composite action | #444 | +| 3.3 | Turbo remote-cache debug step + `test:coverage` as proper task + `ANALYZE` separation | #443 | +| 3.4 | `dorny/paths-filter` per-job gates + tighten `globalDependencies` | #442 | + +### Phase 4 - Synthesis (sequential, end) + +| ID | Task | +|----|------| +| 4.1 | Update CHANGELOG (release-management skill) | +| 4.2 | Author ADR-068 summarizing batch decisions | +| 4.3 | Final quality gate + push + create PRs | + +## 3. Strategy + +**Hybrid + Swarm**: + +- Sequential Phase 0 (unblock main) and Phase 1 (PR hardening). +- Parallel swarm Phase 2 (DX scaffolding) and Phase 3 (DX hygiene). Each task is independent and touches a non-overlapping file set. Use sub-agents per task in the same message to maximize throughput. +- Sequential Phase 4 (synthesis, PRs, ADR). + +**Quality gates** between phases: + +| Gate | Trigger | +|------|---------| +| QG0 | After 0.1: `bash scripts/validate-workflows.sh` exits 0 | +| QG1 | After 1.5: web/worker/reader-core lint+typecheck+test green; Codacy clean | +| QG2 | After Phase 2/3 batches: minimal quality gate per batch | +| QG3 | Final: full `./scripts/quality_gate.sh` | + +## 4. Agent Assignments + +| Skill / Agent | Phase 1 | Phase 2 | Phase 3 | +|---------------|---------|---------|---------| +| `security-code-auditor` | 1.1, 1.2 (review) | 2.7 (review) | - | +| `reader-core` (epub-rendering-and-cfi) | 1.2, 1.3, 1.4 | - | - | +| `testing-strategy` | 1.4 | - | - | +| `github-workflow` | - | 2.4, 2.7, 2.8 | 3.2, 3.4 | +| `agents-md` | - | 2.1, 2.5, 2.6, 2.9 | 3.1 | +| `release-management` | - | 2.2 (llms.txt is docs) | - | +| `cloudflare-worker-api` | 1.2 (review) | - | - | +| `parallel-execution` | - | swarm driver | swarm driver | +| `goap-agent` | orchestrator + 0.3, 4.x | - | - | + +## 5. Execution Plan (per-phase) + +### Phase 0 +1. `git add` working tree; commit via atomic script `chore(observability): adopt traceId on critical UI actions` (closes #439, #441 work). +2. Run `./scripts/quality_gate.sh` subset (lint+typecheck+test). +3. Push; wait for CI green; close #439, #441 via comment. + +### Phase 1 +1. Fetch Codacy findings via API or web (read-only). +2. Re-implement sanitizer with: (a) hard-coded DOMPurify config (no ADD_TAGS leakage), (b) `USE_PROFILES: { html: true }` for HTML mode, (c) explicit tag-allowlist mirror, (d) idempotency test (running twice = same result). +3. Confirm `useReaderEpub.ts` registers the hook once; `epub-loader.ts` registers once (no double-register). Document the choice. +4. Add `packages/reader-core/src/__tests__/sanitizer-idempotent.test.ts`. +5. Push to PR; re-run CI; confirm Codacy clean. + +### Phase 2 (swarm) +Per-task mini-loop: (a) add file(s), (b) wire into existing config, (c) verify, (d) commit with atomic script, (e) push branch. + +### Phase 3 (swarm) +Per-task mini-loop similar to Phase 2. + +### Phase 4 +1. Run full quality gate. +2. Author ADR-068. +3. Create PRs (one per logical group, per AGENTS.md TIER 1). +4. Update CHANGELOG via `release-management` skill. + +## 6. Quality Checklist + +- [x] Plan in `plans/068-...` +- [ ] Working tree committed +- [ ] All 15 issues have either a fix-PR linked or an ADR-accepted wont-fix entry +- [ ] PR #440 merged or rebased to green +- [ ] CI green on main after Phase 0 +- [ ] Quality gates pass after each phase +- [ ] ADR-068 authored +- [ ] No `console.*` reintroduced on critical paths +- [ ] No secrets/credentials introduced +- [ ] No new `any` without isolation