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: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<img src="https://img.shields.io/badge/Storage-IndexedDB_v8-F59E0B" alt="IndexedDB v8">
<img src="https://img.shields.io/badge/PWA-v3.0-5BB974?logo=pwa" alt="PWA v3.0">
<img src="https://img.shields.io/badge/i18n-19_locales-2925_keys-0EA5E9" alt="i18n 19 locales — 2925 keys">
<img src="https://img.shields.io/badge/Tests-6984%2B_%2F_577_files-22C55E" alt="6984+ tests / 577 files">
<img src="https://img.shields.io/badge/Tests-6988%2B_%2F_578_files-22C55E" alt="6988+ tests / 578 files">
<img src="https://img.shields.io/codecov/c/github/qnbs/WorldScript-Studio?logo=codecov&label=Coverage" alt="Codecov Coverage">
<img src="https://img.shields.io/badge/License-MIT-22C55E" alt="License MIT">
<img src="https://img.shields.io/github/actions/workflow/status/qnbs/WorldScript-Studio/.github/workflows/ci.yml?branch=main&logo=github" alt="CI Status">
Expand Down Expand Up @@ -512,7 +512,7 @@ The Settings → AI panel shows a live GPU status badge with adapter details and
| **Document Export** | docx + jszip | Word-compatible `.docx` generation (lazy-loaded) |
| **PWA** | Service Worker + Web App Manifest v3 | Offline support, installability, Workbox chunking |
| **i18n** | Custom React Context (`I18nContext.tsx`) | 2925 keys × 19 locales (de/en/es/fr/it + ar/he/fa RTL Beta + ja/zh/pt/el/fi/sv/hu/is/eu/ru/ko Beta); EN fallback; `localStorage` persistence |
| **Testing** | Vitest 4.x (6984+ tests / 577 files) + Playwright E2E | Unit/integration + cross-browser E2E; Stryker mutation (manual workflow) |
| **Testing** | Vitest 4.x (6988+ tests / 578 files) + Playwright E2E | Unit/integration + cross-browser E2E; Stryker mutation (manual workflow) |
| **Code Quality** | Biome (lint + format) + TypeScript 7 (tsgo) strict | `--error-on-warnings` in CI; zero `any` policy |
| **Visualization** | Force-directed graph | Interactive character relationship network |
| **Desktop** | Tauri v2 | Cross-platform installer; auto-updater via `latest.json` |
Expand Down Expand Up @@ -550,7 +550,7 @@ WorldScript-Studio/
│ ├── sw.js # PWA Service Worker
│ └── manifest.json # PWA Web App Manifest v3
├── tests/
│ ├── unit/ # Vitest unit tests (6984+ tests, 577 files) — count spans tests/, components/, packages/*/tests/, not just this folder
│ ├── unit/ # Vitest unit tests (6988+ tests, 578 files) — count spans tests/, components/, packages/*/tests/, not just this folder
│ │ ├── ai/ # aiSmallModules, aiCoreFallbackPaths
│ │ └── settings/ # WebLlmPanel, AiSections
│ └── e2e/ # Playwright specs + helpers.ts
Expand Down Expand Up @@ -712,7 +712,7 @@ The main pipeline is [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Opt
| `scorecard` | weekly + `main` push | OpenSSF Scorecard — SARIF uploaded to GitHub Code Scanning |

**Current test metrics (2026-08-21, source-synchronized; CI remains authoritative for pass/fail):**
- **6984+ unit tests** across **577 test files** — CI is authoritative for pass/fail
- **6988+ unit tests** across **578 test files** — CI is authoritative for pass/fail
- Coverage thresholds: lines ≥ 80 · branches ≥ 66 · functions ≥ 72 · statements ≥ 78 — enforced in CI (see Codecov badge for live metrics)
- i18n: **2925 keys × 19 locales** (en/de/fr/es/it + ar/he/fa RTL Beta + ja/zh/pt/el/fi/sv/hu/is/eu/ru/ko Beta)

Expand Down
28 changes: 14 additions & 14 deletions scripts/ci-prepush-lowend.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ function report(name, status, detail = '') {
return status;
}

function runCheck(name, run) {
const status = run();
async function runCheck(name, run) {
const status = await run();
report(name, status === 0 ? 'PASS' : 'FAIL');
if (status !== 0) process.exit(status ?? 1);
}

function main() {
async function main() {
const evidenceIndex = process.argv.indexOf('--prepush-evidence-file');
const evidenceFile = evidenceIndex >= 0 ? process.argv[evidenceIndex + 1] : undefined;
const fullRequested = process.argv.includes('--full');
Expand Down Expand Up @@ -51,18 +51,18 @@ function main() {
}
report('Dependency state', 'PASS');

runCheck('Toolchain', () => runNodeScript('scripts/check-pnpm-toolchain.mjs', ['--hook']));
runCheck('Docs/release truth', () => runNodeScript('scripts/check-doc-metrics.mjs'));
runCheck('CSP policy', () => runNodeScript('scripts/check-csp-policy.mjs'));
runCheck('Desktop import boundary', () =>
await runCheck('Toolchain', () => runNodeScript('scripts/check-pnpm-toolchain.mjs', ['--hook']));
await runCheck('Docs/release truth', () => runNodeScript('scripts/check-doc-metrics.mjs'));
await runCheck('CSP policy', () => runNodeScript('scripts/check-csp-policy.mjs'));
await runCheck('Desktop import boundary', () =>
runNodeScript('scripts/check-tauri-import-boundary.mjs'),
);
runCheck('Native readiness', () => runNodeScript('scripts/check-native-readiness.mjs'));
await runCheck('Native readiness', () => runNodeScript('scripts/check-native-readiness.mjs'));

if (shouldRunAdmissionCheck('i18n', classification.files) || full) {
runCheck('i18n key parity', () => runNodeScript('scripts/check-i18n-keys.mjs'));
runCheck('i18n bundle rebuild', () => runNodeScript('scripts/build-i18n.mjs'));
runCheck('i18n translation quality', () =>
await runCheck('i18n key parity', () => runNodeScript('scripts/check-i18n-keys.mjs'));
await runCheck('i18n bundle rebuild', () => runNodeScript('scripts/build-i18n.mjs'));
await runCheck('i18n translation quality', () =>
runNodeScript('scripts/i18n-quality-report.mjs', [
'--strict',
'--min-coverage',
Expand All @@ -74,10 +74,10 @@ function main() {
}

if (shouldRunAdmissionCheck('contentGuard', classification.files) || full)
runCheck('Content guard', () => runNodeScript('scripts/content-guard.mjs'));
await runCheck('Content guard', () => runNodeScript('scripts/content-guard.mjs'));

if (typecheckRequired) {
runCheck('TypeScript (single checker)', () =>
await runCheck('TypeScript (single checker)', () =>
// QNBS-v3: one checker bounds memory use on constrained developer machines.
runLocalBinary('tsgo', ['--project', 'tsconfig.tsgo.json', '--noEmit', '--checkers', '1']),
);
Expand All @@ -92,4 +92,4 @@ function main() {
}

// QNBS-v3: guard execution so this module can be imported for testing without running the CLI.
if (isMainModule(process.argv[1], import.meta.url)) main();
if (isMainModule(process.argv[1], import.meta.url)) await main();
4 changes: 2 additions & 2 deletions scripts/hooks/pre-commit.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import process from 'node:process';
import { ensureDependencyState, runLocalBinary, runNodeScript } from './shared.mjs';

if (runNodeScript('scripts/signing/doctor.mjs', ['--hook']) !== 0) process.exit(1);
if ((await runNodeScript('scripts/signing/doctor.mjs', ['--hook'])) !== 0) process.exit(1);
if (!ensureDependencyState()) process.exit(1);
process.exit(runLocalBinary('lint-staged'));
process.exit(await runLocalBinary('lint-staged'));
4 changes: 2 additions & 2 deletions scripts/hooks/pre-push.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ try {
evidenceFile = join(evidenceDir, 'evidence.json');
writePrePushEvidenceFile(evidenceFile, updates);
const childArgs = [...process.argv.slice(2), '--prepush-evidence-file', evidenceFile];
exitCode = runNodeScript('scripts/signing/verify-outgoing.mjs', childArgs);
if (exitCode === 0) exitCode = runNodeScript('scripts/ci-prepush-lowend.mjs', childArgs);
exitCode = await runNodeScript('scripts/signing/verify-outgoing.mjs', childArgs);
if (exitCode === 0) exitCode = await runNodeScript('scripts/ci-prepush-lowend.mjs', childArgs);
} catch (error) {
console.error(
`pre-push evidence capture failed closed: ${error instanceof Error ? error.message : 'invalid input'}`,
Expand Down
54 changes: 54 additions & 0 deletions scripts/hooks/shared.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export interface BoundedResult {
status: number | null;
signal: string | null;
error: Error | null;
timedOut: boolean;
interrupted: boolean;
command: string;
}

export interface BoundedOptions {
timeoutMs?: number;
env?: NodeJS.ProcessEnv;
input?: string;
shell?: boolean;
cwd?: string;
detached?: boolean;
}

// QNBS-v3: root is a wrapper-only option; runBounded's runtime destructuring never consumes it.
export interface RunOptions extends BoundedOptions {
root?: string;
}

export function ensureDependencyState(root?: string): boolean;

export function runBounded(
command: string,
args: string[],
options?: BoundedOptions,
): Promise<BoundedResult>;
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

export function runNodeScriptDetailed(
script: string,
args?: string[],
options?: RunOptions,
): Promise<BoundedResult>;

export function runNodeScript(
script: string,
args?: string[],
options?: RunOptions,
): Promise<number>;

export function runLocalBinaryDetailed(
binary: string,
args?: string[],
options?: RunOptions,
): Promise<BoundedResult>;

export function runLocalBinary(
binary: string,
args?: string[],
options?: RunOptions,
): Promise<number>;
Loading
Loading