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-6959%2B_%2F_575_files-22C55E" alt="6959+ tests / 575 files">
<img src="https://img.shields.io/badge/Tests-6963%2B_%2F_575_files-22C55E" alt="6963+ tests / 575 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 (6959+ tests / 575 files) + Playwright E2E | Unit/integration + cross-browser E2E; Stryker mutation (manual workflow) |
| **Testing** | Vitest 4.x (6963+ tests / 575 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 (6959+ tests, 575 files) — count spans tests/, components/, packages/*/tests/, not just this folder
│ ├── unit/ # Vitest unit tests (6963+ tests, 575 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):**
- **6959+ unit tests** across **575 test files** — CI is authoritative for pass/fail
- **6963+ unit tests** across **575 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
1 change: 1 addition & 0 deletions scripts/signing/signing-core.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export interface PushEvidence {
updates: PushEvidenceUpdate[];
changedFiles: string[];
evidenceState: 'RESOLVED' | 'INVALID';
pathEvidenceState: 'COMPLETE' | 'PARTIAL';
reason?: string;
}
export function resolvePushEvidence(
Expand Down
12 changes: 11 additions & 1 deletion scripts/signing/signing-core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,22 @@ export function resolvePushEvidence(input, cwd = process.cwd(), dependencies = {
disposition: isZeroSha(update.remoteSha) ? 'NEW_BRANCH' : 'UPDATED',
});
}
return { updates: evidenceUpdates, changedFiles: [...changedFiles], evidenceState: 'RESOLVED' };
// QNBS-v3: tag updates prove object validity but not a complete changed-path set.
const pathEvidenceState = evidenceUpdates.some(({ disposition }) => disposition === 'TAG')
Comment thread
qnbs marked this conversation as resolved.
? 'PARTIAL'
: 'COMPLETE';
return {
updates: evidenceUpdates,
changedFiles: [...changedFiles],
evidenceState: 'RESOLVED',
pathEvidenceState,
};
} catch (error) {
return {
updates: [],
changedFiles: [],
evidenceState: 'INVALID',
pathEvidenceState: 'PARTIAL',
reason: error instanceof Error ? error.message : 'invalid push evidence',
};
}
Expand Down
68 changes: 68 additions & 0 deletions tests/unit/signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ describe('local signing controls', () => {
: ['src/with\t tab.ts', '世界 file.ts', 'src/with\t tab.ts'],
});
expect(result.evidenceState).toBe('RESOLVED');
expect(result.pathEvidenceState).toBe('PARTIAL');
expect(result.updates.map(({ disposition }) => disposition)).toEqual([
'UPDATED',
'NEW_BRANCH',
Expand All @@ -229,6 +230,73 @@ describe('local signing controls', () => {
expect(result.changedFiles).toEqual(['src/with\t tab.ts', '世界 file.ts', 'new\nfile.ts']);
});

// QNBS-v3: branch/new-branch/deletion diff real content; tags can't without inventing paths.
it('reports complete path evidence for branch, new-branch, and deletion updates', () => {
const zero = '0'.repeat(40);
const updates = [
parseRefUpdate(`refs/heads/main ${'a'.repeat(40)} refs/heads/main ${'b'.repeat(40)}`)!,
parseRefUpdate(`refs/heads/new ${'c'.repeat(40)} refs/heads/new ${zero}`)!,
parseRefUpdate(`refs/heads/deleted ${zero} refs/heads/deleted ${'d'.repeat(40)}`)!,
];
const result = resolvePushEvidence(updates, process.cwd(), {
commitExists: () => true,
changedFilesBetween: () => ['src/example.ts'],
});

expect(result.evidenceState).toBe('RESOLVED');
expect(result.pathEvidenceState).toBe('COMPLETE');
});

it('marks lightweight and annotated tag evidence partial without inventing paths', () => {
const commit = 'a'.repeat(40);
const lightweight = parseRefUpdate(`refs/tags/v1 ${commit} refs/tags/v1 ${'0'.repeat(40)}`)!;
const annotated = parseRefUpdate(
`refs/tags/v2 ${'b'.repeat(40)} refs/tags/v2 ${'c'.repeat(40)}`,
)!;

for (const update of [lightweight, annotated]) {
const result = resolvePushEvidence([update], process.cwd(), {
objectExists: () => true,
});
expect(result.evidenceState).toBe('RESOLVED');
expect(result.pathEvidenceState).toBe('PARTIAL');
expect(result.changedFiles).toEqual([]);
}
});

it('marks mixed branch and tag evidence partial', () => {
const zero = '0'.repeat(40);
const result = resolvePushEvidence(
[
parseRefUpdate(`refs/heads/main ${'a'.repeat(40)} refs/heads/main ${'b'.repeat(40)}`)!,
parseRefUpdate(`refs/tags/v1 ${'c'.repeat(40)} refs/tags/v1 ${zero}`)!,
],
process.cwd(),
{
commitExists: () => true,
objectExists: () => true,
changedFilesBetween: () => ['src/a.ts'],
},
);

expect(result.evidenceState).toBe('RESOLVED');
expect(result.pathEvidenceState).toBe('PARTIAL');
expect(result.changedFiles).toEqual(['src/a.ts']);
});

it('keeps empty evidence complete and rejects an unavailable tag object', () => {
expect(resolvePushEvidence([]).pathEvidenceState).toBe('COMPLETE');

const result = resolvePushEvidence(
[parseRefUpdate(`refs/tags/v1 ${'a'.repeat(40)} refs/tags/v1 ${'0'.repeat(40)}`)!],
process.cwd(),
{ objectExists: () => false },
);

expect(result.evidenceState).toBe('INVALID');
expect(result.pathEvidenceState).toBe('PARTIAL');
});

it('fails closed for missing objects, bases, Git failures, and unsupported evidence', () => {
const update = parseRefUpdate(
`refs/heads/main ${'a'.repeat(40)} refs/heads/main ${'b'.repeat(40)}`,
Expand Down
Loading