diff --git a/.cursor/rules/graphify.mdc b/.cursor/rules/graphify.mdc new file mode 100644 index 000000000..867031495 --- /dev/null +++ b/.cursor/rules/graphify.mdc @@ -0,0 +1,21 @@ +--- +description: graphify knowledge graph context +alwaysApply: true +--- + +This project can build a Graphify knowledge graph under `graphify-out/` (generated locally; not committed). Scope/exclusions: `.graphifyignore` and `code/.graphifyignore`. + +**When exploring architecture, prefer Graphify if a local graph exists:** +- `graphify query ""` — scoped subgraph for codebase or architecture questions +- `graphify path "" ""` — dependency path between two symbols +- `graphify explain ""` — nodes related to a concept + +If `graphify-out/graph.json` is missing, regenerate with AST-only Graphify against `code/` (respect `.graphifyignore`; do not ingest PDFs), then query. Treat Graphify edges as hints — always source-verify before concluding. + +Only use Read/Grep/Glob directly when: +1. Graphify has already oriented you and you need specific lines +2. Graphify is unavailable and regeneration is not practical + +- If `graphify-out/wiki/index.md` exists, navigate it instead of reading raw files +- Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review when query/path/explain are insufficient +- After substantial code changes, run `graphify update .` locally to refresh the graph (AST-only) diff --git a/.github/workflows/webmcp-ci.yml b/.github/workflows/webmcp-ci.yml index 72b347d15..c15d3c7cd 100644 --- a/.github/workflows/webmcp-ci.yml +++ b/.github/workflows/webmcp-ci.yml @@ -20,79 +20,93 @@ jobs: webmcp-release-gate: name: "WebMCP Gate (build + unit tests)" runs-on: ubuntu-latest + defaults: + run: + working-directory: code steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 with: - node-version: 20 - cache: npm + version: 10.28.2 - - name: Install root dependencies - run: npm ci + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: code/pnpm-lock.yaml - - name: Install extension dependencies - run: npm ci - working-directory: code/apps/extension-chromium + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Build extension - run: npm run build - working-directory: code/apps/extension-chromium + run: pnpm --filter @optimandoai/extension-chromium run build - name: Run WebMCP unit + sender-gate tests - run: npm run test:webmcp:ci - working-directory: code + run: pnpm run test:webmcp:ci quarantine-monitor: name: "Quarantine (pre-existing failures, non-blocking)" runs-on: ubuntu-latest continue-on-error: true + defaults: + run: + working-directory: code steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 with: - node-version: 20 - cache: npm + version: 10.28.2 - - name: Install root dependencies - run: npm ci + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: code/pnpm-lock.yaml - - name: Install extension dependencies - run: npm ci - working-directory: code/apps/extension-chromium + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Run quarantined tests (informational) - run: npm run test:quarantine || true - working-directory: code + run: pnpm run test:quarantine || true webmcp-e2e-smoke: name: "WebMCP E2E Smoke (opt-in)" runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' && inputs.run_e2e == true }} + defaults: + run: + working-directory: code steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 with: - node-version: 20 - cache: npm + version: 10.28.2 - - name: Install root dependencies - run: npm ci + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: code/pnpm-lock.yaml - - name: Install extension dependencies - run: npm ci - working-directory: code/apps/extension-chromium + - name: Install dependencies + run: pnpm install --frozen-lockfile - name: Install Playwright browsers - run: npx playwright install --with-deps chromium + run: pnpm exec playwright install --with-deps chromium working-directory: code/apps/extension-chromium - name: Build extension - run: npm run build - working-directory: code/apps/extension-chromium + run: pnpm --filter @optimandoai/extension-chromium run build - name: Run WebMCP E2E smoke - run: npm run test:e2e:webmcp + run: pnpm run test:e2e:webmcp working-directory: code/apps/extension-chromium diff --git a/.gitignore b/.gitignore index 15c294348..2597eeca8 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,18 @@ code/packages/ingestion-core/src/*.js # Chromium extension Vite outDir (versioned per release, e.g. build55647) code/apps/extension-chromium/build*/ + +# graphify generated outputs + caches (regenerate with `graphify` / graphifyy) +# Keep hand-authored: .graphifyignore, code/.graphifyignore, .cursor/rules/graphify.mdc +graphify-out/ +code/graphify-out/ + +# Cursor tooling artefacts (agent state, dumps). The Graphify↔Cursor +# integration is hand-authored and must stay visible: the always-on rule in +# .cursor/rules/ and, when used, .cursor/mcp.json. +.cursor/* +!.cursor/rules/ +!.cursor/mcp.json + +# Accidental vitest JSON dumps (not source) +code/phase2-before-native.json diff --git a/.graphifyignore b/.graphifyignore new file mode 100644 index 000000000..be9b31c08 --- /dev/null +++ b/.graphifyignore @@ -0,0 +1,34 @@ +# Initial graphify pass: codebase AST only. +# PDFs and documentation are excluded; add selectively later with /graphify --update. +*.pdf +**/*.pdf +*.md +**/*.md +*.mdx +**/*.mdx +*.txt +**/*.txt +*.html +**/*.html +*.rst +**/*.rst +*.qmd +**/*.qmd +*.yaml +**/*.yaml +*.yml +**/*.yml +*.png +**/*.png +*.jpg +**/*.jpg +*.jpeg +**/*.jpeg +*.webp +**/*.webp +*.gif +**/*.gif +*.svg +**/*.svg +**/THIRD_PARTY_LICENSES/** +**/docs/** diff --git a/code/.gitignore b/code/.gitignore index 6800ac712..88f28afca 100644 --- a/code/.gitignore +++ b/code/.gitignore @@ -338,3 +338,7 @@ apps/extension-chromium/build179/ apps/extension-chromium/build180/ apps/extension-chromium/build181/ apps/extension-chromium/build182/ + +# Accidental vitest JSON dumps (not source) +phase2-before-native.json +*-before-native.json diff --git a/code/.graphifyignore b/code/.graphifyignore new file mode 100644 index 000000000..be9b31c08 --- /dev/null +++ b/code/.graphifyignore @@ -0,0 +1,34 @@ +# Initial graphify pass: codebase AST only. +# PDFs and documentation are excluded; add selectively later with /graphify --update. +*.pdf +**/*.pdf +*.md +**/*.md +*.mdx +**/*.mdx +*.txt +**/*.txt +*.html +**/*.html +*.rst +**/*.rst +*.qmd +**/*.qmd +*.yaml +**/*.yaml +*.yml +**/*.yml +*.png +**/*.png +*.jpg +**/*.jpg +*.jpeg +**/*.jpeg +*.webp +**/*.webp +*.gif +**/*.gif +*.svg +**/*.svg +**/THIRD_PARTY_LICENSES/** +**/docs/** diff --git a/code/apps/electron-vite-project/electron-builder.config.cjs b/code/apps/electron-vite-project/electron-builder.config.cjs index 8b77ca0b4..fbcdf62aa 100644 --- a/code/apps/electron-vite-project/electron-builder.config.cjs +++ b/code/apps/electron-vite-project/electron-builder.config.cjs @@ -7,12 +7,18 @@ const appDir = __dirname /** * Parsed by scripts/kill-wr-desk.cjs — must contain a line matching: - * return 'C:\\build-output\\build044' + * return 'C:\\build-output\\build009' */ function windowsOutputDirMarker() { - return 'C:\\build-output\\build044' + return 'C:\\build-output\\build009' } +/** + * `pnpm run build:wrc-test` packages into its own `…-wrc-test` folder, so an + * existing release build is kept. The Windows build scripts apply the same suffix. + */ +const WRC_TEST_OUTPUT_SUFFIX = process.env.WRDESK_WRC_BUILD_FLAVOR === 'wrc-test' ? '-wrc-test' : '' + const workspaceRoot = path.resolve(appDir, '../..') function findFile(candidates) { @@ -61,12 +67,16 @@ module.exports = { /** Avoid winCodeSign tool download/extract (fails on Windows without symlink privilege for darwin symlinks in the archive). */ forceCodeSigning: false, directories: { - output: process.platform === 'win32' ? windowsOutputDirMarker() : path.join(appDir, 'dist', 'release'), + output: + process.platform === 'win32' + ? windowsOutputDirMarker() + WRC_TEST_OUTPUT_SUFFIX + : path.join(appDir, 'dist', `release${WRC_TEST_OUTPUT_SUFFIX}`), buildResources: 'build', }, files: [ 'dist/**/*', '!dist/release/**', + '!dist/release-wrc-test/**', 'dist-electron/**/*', 'package.json', 'node_modules/**/*', diff --git a/code/apps/electron-vite-project/electron/WRExpert.default.md b/code/apps/electron-vite-project/electron/WRExpert.default.md index b4961953a..c6ccbb860 100644 --- a/code/apps/electron-vite-project/electron/WRExpert.default.md +++ b/code/apps/electron-vite-project/electron/WRExpert.default.md @@ -1,4 +1,4 @@ -# WRExpert.md — WR Desk Inbox AI Behaviour +# WRExpert.md — Optirando Inbox AI Behaviour # This is your personal AI expert. Edit this file to teach the AI how to # handle your specific inbox. Changes take effect on the next Auto-Sort run. # Lines starting with # are comments and are ignored by the AI. @@ -36,7 +36,7 @@ Move here if: - Meeting notes, summaries, reports for future reference - Any email explicitly marked by the user as "keep" -### urgent (WR Desk Urgent tab + mirrored to server **Urgent** folder on sync, urgency >= 7) +### urgent (Optirando Urgent tab + mirrored to server **Urgent** folder on sync, urgency >= 7) Move here if ANY of these apply: - Invoice or payment overdue or due within 3 days - Legal deadline within 7 days @@ -44,13 +44,13 @@ Move here if ANY of these apply: - Security alert requiring immediate action - Direct request from a known important contact requiring same-day response -### action_required (WR Desk Important flow + mirrored to **Pending Review** on sync, urgency 4–6) +### action_required (Optirando Important flow + mirrored to **Pending Review** on sync, urgency 4–6) Move here if: - Requires a response within the next 7 days - Requires a decision or manual step (not just reading) - Contains a question directed at you that is not automated -### normal (WR Desk Normal / All until archived; mirrored to **Archive** on sync when classified) +### normal (Optirando Normal / All until archived; mirrored to **Archive** on sync when classified) Move here if: - Requires attention but no urgency - Does not fit the above categories diff --git a/code/apps/electron-vite-project/electron/__tests__/handshakeAcceptSafeOpts.regression.test.ts b/code/apps/electron-vite-project/electron/__tests__/handshakeAcceptSafeOpts.regression.test.ts index 6fc033410..305da8639 100644 --- a/code/apps/electron-vite-project/electron/__tests__/handshakeAcceptSafeOpts.regression.test.ts +++ b/code/apps/electron-vite-project/electron/__tests__/handshakeAcceptSafeOpts.regression.test.ts @@ -67,4 +67,12 @@ describe('buildHandshakeAcceptSafeOpts (preload handshake:accept)', () => { const s = buildHandshakeAcceptSafeOpts({ device_role: 'host' }) expect(s).toEqual({ device_role: 'host' }) }) + + it('T10_forwards_expected_preview_hash_only_as_a_64_char_lowercase_hex_digest', () => { + const hash = 'ab'.repeat(32) + expect(buildHandshakeAcceptSafeOpts({ expected_preview_hash: ` ${hash} ` })).toEqual({ expected_preview_hash: hash }) + for (const bad of ['AB'.repeat(32), 'ab'.repeat(31), `${hash}00`, 'not-a-hash', 42, { hash }]) { + expect(buildHandshakeAcceptSafeOpts({ expected_preview_hash: bad })).toEqual({}) + } + }) }) diff --git a/code/apps/electron-vite-project/electron/handshakeAcceptSafeOpts.ts b/code/apps/electron-vite-project/electron/handshakeAcceptSafeOpts.ts index 8ad2416ba..bc4df5948 100644 --- a/code/apps/electron-vite-project/electron/handshakeAcceptSafeOpts.ts +++ b/code/apps/electron-vite-project/electron/handshakeAcceptSafeOpts.ts @@ -1,8 +1,8 @@ /** * Pure builder for `handshake:accept` IPC options — shared with preload tests. * Forwards an explicit allowlist only (no pass-through of arbitrary objects). - * Internal vs normal and X25519 requirements are decided in main using persisted - * `record.handshake_type` — this module does not use `device_role` as proof of internal. + * Internal vs normal and X25519 requirements are decided in main using the persisted + * `record.same_principal` flag — this module does not use `device_role` as proof of internal. */ const MAX_B64 = 8192 @@ -95,5 +95,9 @@ export function buildHandshakeAcceptSafeOpts(contextOpts: unknown): Record