From 7f5db953fa4adb6c047ff0dd50c3fda9b0f0072f Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Tue, 19 May 2026 02:49:55 +0700 Subject: [PATCH] chore: update dependencies and improve code formatting - Added `vendor/discord-video-stream` to pnpm workspace. - Refactored `llmModerationClient.ts` for better readability and consistency. - Adjusted imports in `recordingsRoutes.ts` for clarity. - Updated `webserver.ts` to correctly import `createRecordingsRoutes`. - Enhanced test cases in `llmModerationClient.test.ts` for improved readability. - Updated submodule references for `better-sqlite3`, `discord-video-stream`, `discord.js-selfbot-v13`, `drizzle-orm`, and `node-datachannel`. - Created documentation for deprecated dependency removal plan and design. --- ...026-05-19-deprecated-dependency-removal.md | 597 ++++++++++++++++ .../2026-05-19-winston-logging-refactor.md | 652 ++++++++++++++++++ ...19-deprecated-dependency-removal-design.md | 91 +++ pnpm-workspace.yaml | 1 + src/moderation/llmModerationClient.ts | 10 +- src/routes/recordingsRoutes.ts | 4 +- src/webserver.ts | 2 +- tests/moderation/llmModerationClient.test.ts | 42 +- vendor/better-sqlite3 | 2 +- vendor/discord-video-stream | 2 +- vendor/drizzle-orm | 2 +- vendor/node-datachannel | 2 +- 12 files changed, 1384 insertions(+), 23 deletions(-) create mode 100644 docs/superpowers/plans/2026-05-19-deprecated-dependency-removal.md create mode 100644 docs/superpowers/plans/2026-05-19-winston-logging-refactor.md create mode 100644 docs/superpowers/specs/2026-05-19-deprecated-dependency-removal-design.md diff --git a/docs/superpowers/plans/2026-05-19-deprecated-dependency-removal.md b/docs/superpowers/plans/2026-05-19-deprecated-dependency-removal.md new file mode 100644 index 0000000..0c44cdf --- /dev/null +++ b/docs/superpowers/plans/2026-05-19-deprecated-dependency-removal.md @@ -0,0 +1,597 @@ +# Deprecated Dependency Removal Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove deprecated packages from `pnpm-lock.yaml` where maintained replacements exist, and vendor/submodule only when no clean replacement exists. + +**Architecture:** Treat dependency cleanup as package graph surgery: identify owner, change one dependency source at a time, reinstall, verify lockfile, then run project checks. Existing vendored workspaces stay source of truth for selfbot and discord-video-stream patches. + +**Tech Stack:** Node.js, pnpm workspaces, TypeScript, Vitest, Biome, git submodules. + +--- + +## File Structure + +- Modify: `package.json` — root dependency versions, workspace references, `pnpm.onlyBuiltDependencies`, optional `pnpm.overrides` if needed. +- Modify: `pnpm-workspace.yaml` — workspace package list for any new submodule/vendor package. +- Modify: `pnpm-lock.yaml` — regenerated by `pnpm install` only. +- Modify: `vendor/discord-video-stream/package.json` — dev dependency on `discord.js-selfbot-v13` should use workspace package instead of registry. +- Modify: `vendor/discord.js-selfbot-v13/package.json` — replace deprecated `otplib@12` chain if compatible. +- Possibly create: `vendor/` — only if package has no maintained replacement and strict cleanup still needs local patching. + +## Deprecated Package Owners + +Known current owners: + +```text +drizzle-kit -> @esbuild-kit/esm-loader -> @esbuild-kit/core-utils +discord.js-selfbot-v13 -> otplib@12 -> @otplib/plugin-crypto, @otplib/plugin-thirty-two, @otplib/preset-default +@discordjs/opus -> @discordjs/node-pre-gyp -> npmlog, are-we-there-yet, gauge, rimraf@3, glob@7, inflight +better-sqlite3 -> prebuild-install +@lng2004/node-datachannel -> prebuild-install +``` + +## Task 1: Establish Baseline + +**Files:** +- Read: `package.json` +- Read: `pnpm-workspace.yaml` +- Read: `vendor/discord-video-stream/package.json` +- Read: `vendor/discord.js-selfbot-v13/package.json` + +- [ ] **Step 1: Capture current git state** + +Run: + +```bash +git status --short +``` + +Expected: includes existing intended changes for `package.json`, `pnpm-workspace.yaml`, `pnpm-lock.yaml`, `.gitmodules`, and `vendor/discord-video-stream`. Do not revert user changes. + +- [ ] **Step 2: Capture dependency owners** + +Run: + +```bash +pnpm why discord.js-selfbot-v13 @esbuild-kit/core-utils @esbuild-kit/esm-loader @otplib/plugin-crypto @otplib/plugin-thirty-two @otplib/preset-default are-we-there-yet fs-then-native gauge inflight lodash.pick npmlog prebuild-install stream-connect test-value +``` + +Expected: output maps deprecated packages to direct owners. Save relevant owner names in notes for next tasks. + +- [ ] **Step 3: Capture install warning baseline** + +Run: + +```bash +pnpm install +``` + +Expected: install completes. Warnings may mention deprecated transitive packages. + +- [ ] **Step 4: Capture baseline checks** + +Run: + +```bash +pnpm run typecheck +pnpm run test +``` + +Expected: both pass before dependency changes. If failing, stop and report exact failures before continuing. + +## Task 2: Patch `discord-video-stream` Workspace Selfbot Reference + +**Files:** +- Modify: `vendor/discord-video-stream/package.json` +- Modify: `pnpm-lock.yaml` + +- [ ] **Step 1: Inspect current devDependency** + +Open `vendor/discord-video-stream/package.json` and find: + +```json +"discord.js-selfbot-v13": "^3.7.1" +``` + +Expected: exists under `devDependencies`. + +- [ ] **Step 2: Replace devDependency with workspace reference** + +Change that entry to: + +```json +"discord.js-selfbot-v13": "workspace:*" +``` + +Keep peer dependency unchanged: + +```json +"peerDependencies": { + "discord.js-selfbot-v13": "^3.6.0" +} +``` + +- [ ] **Step 3: Reinstall** + +Run: + +```bash +pnpm install +``` + +Expected: lockfile uses local workspace for `discord.js-selfbot-v13` in `vendor/discord-video-stream` importer. + +- [ ] **Step 4: Verify no registry selfbot fetch from discord-video-stream** + +Run: + +```bash +pnpm why discord.js-selfbot-v13 +``` + +Expected: root and discord-video-stream both point to `link:vendor/discord.js-selfbot-v13` or workspace link. + +- [ ] **Step 5: Run checks** + +Run: + +```bash +pnpm run typecheck +pnpm run test +``` + +Expected: both pass. + +## Task 3: Upgrade or Patch `otplib` Chain in Vendored Selfbot + +**Files:** +- Modify: `vendor/discord.js-selfbot-v13/package.json` +- Modify: `pnpm-lock.yaml` + +- [ ] **Step 1: Locate selfbot otplib dependency** + +Run: + +```bash +node -e "const p=require('./vendor/discord.js-selfbot-v13/package.json'); console.log(p.dependencies?.otplib || p.devDependencies?.otplib)" +``` + +Expected: prints `^12.x` or `12.x`. + +- [ ] **Step 2: Check latest otplib version** + +Run: + +```bash +npm view otplib version deprecated --json +``` + +Expected: latest version is not deprecated. + +- [ ] **Step 3: Change selfbot dependency to otplib latest major** + +In `vendor/discord.js-selfbot-v13/package.json`, replace existing `otplib` dependency value with latest non-deprecated major range. Example if latest is 13.x: + +```json +"otplib": "^13.0.0" +``` + +Do not change package name or selfbot exports. + +- [ ] **Step 4: Reinstall** + +Run: + +```bash +pnpm install +``` + +Expected: install completes. `@otplib/plugin-crypto`, `@otplib/plugin-thirty-two`, and `@otplib/preset-default` should disappear if otplib v13 no longer pulls them. + +- [ ] **Step 5: Verify otplib deprecated plugins gone** + +Run: + +```bash +pnpm why @otplib/plugin-crypto @otplib/plugin-thirty-two @otplib/preset-default +``` + +Expected: no dependency path for those packages. If they remain under `otplib`, inspect latest otplib metadata and stop before vendoring otplib. + +- [ ] **Step 6: Run checks** + +Run: + +```bash +pnpm run typecheck +pnpm run test +``` + +Expected: both pass. If selfbot code breaks due otplib API changes, revert only the otplib version change and report API mismatch. + +## Task 4: Upgrade `drizzle-kit` to Remove `@esbuild-kit/*` + +**Files:** +- Modify: `package.json` +- Modify: `pnpm-lock.yaml` + +- [ ] **Step 1: Check current and latest drizzle-kit** + +Run: + +```bash +node -e "const p=require('./package.json'); console.log(p.devDependencies['drizzle-kit'])" +npm view drizzle-kit version deprecated --json +``` + +Expected: latest version is not deprecated. + +- [ ] **Step 2: Update root devDependency if newer version exists** + +If latest version is newer than `0.31.10`, change `package.json` devDependency: + +```json +"drizzle-kit": "^" +``` + +Example: + +```json +"drizzle-kit": "^0.32.0" +``` + +Use actual latest version from npm output. + +- [ ] **Step 3: Reinstall** + +Run: + +```bash +pnpm install +``` + +Expected: install completes. + +- [ ] **Step 4: Verify esbuild-kit packages gone** + +Run: + +```bash +pnpm why @esbuild-kit/core-utils @esbuild-kit/esm-loader +``` + +Expected: no dependency path. If latest `drizzle-kit` still pulls them, keep latest only if project checks pass; do not vendor `drizzle-kit` unless user confirms dev-only strictness. + +- [ ] **Step 5: Run drizzle commands** + +Run: + +```bash +pnpm run db:generate -- --help +pnpm run db:migrate -- --help +``` + +Expected: commands print help or usage without crashing. Do not run actual migrations. + +- [ ] **Step 6: Run checks** + +Run: + +```bash +pnpm run typecheck +pnpm run test +``` + +Expected: both pass. + +## Task 5: Remove or Replace Direct `@discordjs/opus` + +**Files:** +- Modify: `package.json` +- Modify: `pnpm-lock.yaml` +- Inspect: `src/recorder/decoder.ts` +- Inspect: `tests/decoder.test.ts` + +- [ ] **Step 1: Find project usage of `@discordjs/opus`** + +Run: + +```bash +grep -R "@discordjs/opus\|OpusEncoder\|OpusDecoder" -n src tests package.json +``` + +Expected: usage locations show whether direct package is imported by project code or only required indirectly by `prism-media`. + +- [ ] **Step 2: Check `@discordjs/voice` encryption/audio requirements** + +Run: + +```bash +pnpm why @discordjs/opus prism-media +``` + +Expected: shows `@discordjs/voice` and root dependency relationships. + +- [ ] **Step 3: Test removal in package manifest** + +Remove root dependency line from `package.json`: + +```json +"@discordjs/opus": "^0.10.0", +``` + +Do not edit code yet. + +- [ ] **Step 4: Reinstall** + +Run: + +```bash +pnpm install +``` + +Expected: install completes. If install or peer resolution fails, restore `@discordjs/opus` and continue to Step 8. + +- [ ] **Step 5: Run decoder-specific tests** + +Run: + +```bash +pnpm run test -- tests/decoder.test.ts +``` + +Expected: tests pass or skip native opus gracefully. If tests fail because native opus is required by project behavior, restore `@discordjs/opus` and continue to Step 8. + +- [ ] **Step 6: Run full checks** + +Run: + +```bash +pnpm run typecheck +pnpm run test +``` + +Expected: both pass. + +- [ ] **Step 7: Verify deprecated node-pre-gyp chain gone** + +Run: + +```bash +pnpm why @discordjs/node-pre-gyp npmlog are-we-there-yet gauge rimraf glob inflight +``` + +Expected: no dependency path through `@discordjs/opus`. If gone, task complete. + +- [ ] **Step 8: If `@discordjs/opus` is required, try maintained alternatives** + +Run: + +```bash +npm view opusscript version deprecated --json +npm view @evan/opus version deprecated --json +``` + +Expected: identify non-deprecated candidate. Do not switch unless package supports same runtime path used by `prism-media` or direct project imports. + +- [ ] **Step 9: If no compatible maintained alternative exists, vendor decision checkpoint** + +Stop and report: + +```text +@discordjs/opus still required. No compatible maintained replacement verified. Next action requires cloning/vendoring smallest package owner or accepting deprecated native install chain. +``` + +Do not clone without user confirmation of target repository. + +## Task 6: Evaluate `prebuild-install` Owners + +**Files:** +- Modify: `package.json` only if safe upgrade exists +- Modify: `vendor/discord-video-stream/package.json` only if safe upgrade exists +- Modify: `pnpm-lock.yaml` + +- [ ] **Step 1: Check owner versions** + +Run: + +```bash +npm view better-sqlite3 version deprecated --json +npm view @lng2004/node-datachannel version deprecated --json +node -e "const root=require('./package.json'); const dvs=require('./vendor/discord-video-stream/package.json'); console.log({betterSqlite3: root.dependencies['better-sqlite3'], nodeDatachannel: dvs.dependencies['@lng2004/node-datachannel']})" +``` + +Expected: latest versions known. + +- [ ] **Step 2: Upgrade `better-sqlite3` if newer version exists** + +If latest is newer than current, change `package.json`: + +```json +"better-sqlite3": "^" +``` + +Use actual latest version. + +- [ ] **Step 3: Upgrade `@lng2004/node-datachannel` if newer version exists** + +If latest is newer than current and package name still matches discord-video-stream requirements, change `vendor/discord-video-stream/package.json`: + +```json +"@lng2004/node-datachannel": "" +``` + +Use exact latest version only if upstream uses exact published builds. + +- [ ] **Step 4: Reinstall** + +Run: + +```bash +pnpm install +``` + +Expected: native dependencies install or reuse existing builds successfully. + +- [ ] **Step 5: Verify `prebuild-install` status** + +Run: + +```bash +pnpm why prebuild-install +``` + +Expected: either no dependency path, or only native owners remain. + +- [ ] **Step 6: Run checks** + +Run: + +```bash +pnpm run typecheck +pnpm run test +``` + +Expected: both pass. + +- [ ] **Step 7: If `prebuild-install` remains** + +Stop and report owner paths. Do not vendor native packages unless no maintained version removes it and user explicitly wants native submodule maintenance. + +## Task 7: Final Deprecation Audit + +**Files:** +- Modify: `pnpm-lock.yaml` through install only + +- [ ] **Step 1: Run clean install audit** + +Run: + +```bash +pnpm install +``` + +Expected: no deprecated package warnings where maintained replacements were applied. + +- [ ] **Step 2: Run owner query for all known deprecated names** + +Run: + +```bash +pnpm why discord.js-selfbot-v13 @esbuild-kit/core-utils @esbuild-kit/esm-loader @otplib/plugin-crypto @otplib/plugin-thirty-two @otplib/preset-default are-we-there-yet fs-then-native gauge inflight lodash.pick npmlog prebuild-install stream-connect test-value @discordjs/node-pre-gyp +``` + +Expected: no paths for packages removed by prior tasks. Remaining paths must be only approved unavoidable native/package-owner cases. + +- [ ] **Step 3: Check npm deprecation metadata for remaining suspect packages** + +Run: + +```bash +node - <<'NODE' +const {execFileSync}=require('child_process'); +const pkgs=['discord.js-selfbot-v13','@esbuild-kit/core-utils','@esbuild-kit/esm-loader','@otplib/plugin-crypto','@otplib/plugin-thirty-two','@otplib/preset-default','are-we-there-yet','fs-then-native','gauge','inflight','lodash.pick','npmlog','prebuild-install','stream-connect','test-value','@discordjs/node-pre-gyp']; +for (const p of pkgs) { + try { + const out=execFileSync('npm',['view',p,'deprecated','--json'],{encoding:'utf8'}).trim(); + if (out && out !== 'null') console.log(`${p}: ${JSON.parse(out)}`); + } catch { + console.log(`${p}: npm view failed`); + } +} +NODE +``` + +Expected: command prints metadata only. Compare printed package names with `pnpm why` output. + +- [ ] **Step 4: Run final checks** + +Run: + +```bash +pnpm run typecheck +pnpm run test +pnpm run lint +``` + +Expected: all pass. + +## Task 8: Document Remaining Unavoidable Deprecated Packages + +**Files:** +- Modify: `docs/superpowers/specs/2026-05-19-deprecated-dependency-removal-design.md` + +- [ ] **Step 1: If no deprecated packages remain, append success note** + +Append this section: + +```markdown +## Final Audit Result + +All known deprecated packages from the initial audit were removed from the active pnpm dependency graph. +``` + +- [ ] **Step 2: If deprecated packages remain, append owner note** + +Append this section with actual owner paths from `pnpm why`: + +```markdown +## Final Audit Result + +Remaining deprecated packages after maintained upgrade attempts: + +- `` remains via ``. Reason: ``. + +These are candidates for future vendoring/submodule patching if strict lockfile cleanup remains required. +``` + +- [ ] **Step 3: Run final git diff review** + +Run: + +```bash +git diff -- package.json pnpm-workspace.yaml pnpm-lock.yaml vendor/discord-video-stream/package.json vendor/discord.js-selfbot-v13/package.json docs/superpowers/specs/2026-05-19-deprecated-dependency-removal-design.md +``` + +Expected: diff contains only dependency migration changes and audit note. + +## Task 9: Commit Checkpoint Only If User Requests Commit + +**Files:** +- Stage only changed package/spec/submodule files relevant to dependency cleanup. + +- [ ] **Step 1: Show status** + +Run: + +```bash +git status --short +``` + +Expected: changed files match work done. + +- [ ] **Step 2: Ask before committing** + +Ask user whether to commit. Do not commit unless explicitly requested. + +- [ ] **Step 3: If user asks to commit, create commit** + +Use exact changed file list, not `git add -A`. Commit message: + +```bash +git commit -m "$(cat <<'EOF' +chore: remove deprecated dependency graph entries + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +Expected: commit succeeds without bypassing hooks. + +## Self-Review + +- Spec coverage: plan covers replace-first, workspace patches, submodule/vendor checkpoint, verification, and final audit documentation. +- Placeholder scan: no TBD/TODO placeholders; steps include exact commands and expected outcomes. +- Type consistency: paths and package names match current workspace files and dependency owners. diff --git a/docs/superpowers/plans/2026-05-19-winston-logging-refactor.md b/docs/superpowers/plans/2026-05-19-winston-logging-refactor.md new file mode 100644 index 0000000..a320a3a --- /dev/null +++ b/docs/superpowers/plans/2026-05-19-winston-logging-refactor.md @@ -0,0 +1,652 @@ +# Winston Logging Refactor Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace Pino logging with Winston, normalize log levels, and write readable console plus JSON file logs without changing application behavior. + +**Architecture:** Keep `src/logger.ts` as the single logging entry point. Rebuild it around one Winston logger with child context support, centralized metadata/error serialization, colorized console output, and JSON file transports. Keep call-site changes minimal and only adjust imports/usages that fail after the backend swap. + +**Tech Stack:** TypeScript, Node.js ESM, Winston, Zod config validation, pnpm, Vitest, Biome. + +--- + +## File Structure + +- Modify: `package.json` — replace Pino dependencies with Winston. +- Modify: `pnpm-lock.yaml` — update via pnpm after dependency changes. +- Modify: `src/config.ts` — expand `LOG_LEVEL` enum to Winston npm levels. +- Modify: `src/logger.ts` — replace Pino implementation with Winston implementation. +- Modify: `.gitignore` — ignore runtime `logs/` directory. +- Test: `tests/logger.test.ts` — add focused logger behavior tests for level validation, error serialization, and file output format helpers. +- Inspect and modify only if needed: files importing `createChildLogger` or `logger`. + +## Important Git Note + +The prior design-spec commit unexpectedly included vendor submodule entries that were already staged in the working tree. Before making implementation commits, run `git status --short` and do not stage unrelated vendor or existing user changes. Stage only files touched by this plan. + +--- + +### Task 1: Add Logger Tests Before Migration + +**Files:** +- Create: `tests/logger.test.ts` +- Modify: none + +- [ ] **Step 1: Write tests for Winston-compatible logger behavior** + +Create `tests/logger.test.ts` with this content: + +```ts +import { describe, expect, it } from "vitest"; +import { formatLogMetadataForTest, serializeLogValueForTest } from "../src/logger"; + +class TestError extends Error { + public code = "TEST_CODE"; + public statusCode = 418; + + constructor() { + super("test failure"); + this.name = "TestError"; + } +} + +describe("logger serialization", () => { + it("serializes Error values with stable fields", () => { + const serialized = serializeLogValueForTest(new TestError()); + + expect(serialized).toMatchObject({ + name: "TestError", + message: "test failure", + code: "TEST_CODE", + statusCode: 418, + }); + expect(serialized).toHaveProperty("stack"); + }); + + it("serializes nested error metadata keys", () => { + const error = new TestError(); + + expect(formatLogMetadataForTest({ error, err: error, reason: error })).toMatchObject({ + error: { + name: "TestError", + message: "test failure", + code: "TEST_CODE", + statusCode: 418, + }, + err: { + name: "TestError", + message: "test failure", + code: "TEST_CODE", + statusCode: 418, + }, + reason: { + name: "TestError", + message: "test failure", + code: "TEST_CODE", + statusCode: 418, + }, + }); + }); + + it("preserves plain metadata", () => { + expect( + formatLogMetadataForTest({ context: "bot", signal: "SIGINT", count: 2 }), + ).toEqual({ context: "bot", signal: "SIGINT", count: 2 }); + }); +}); +``` + +- [ ] **Step 2: Run test to verify current implementation fails** + +Run: + +```bash +pnpm vitest run tests/logger.test.ts +``` + +Expected: FAIL because `formatLogMetadataForTest` and `serializeLogValueForTest` are not exported from `src/logger.ts` yet. + +- [ ] **Step 3: Commit failing tests only** + +```bash +git add tests/logger.test.ts +git commit -m "test: cover logger metadata serialization" +``` + +Expected: commit contains only `tests/logger.test.ts`. + +--- + +### Task 2: Update Dependencies and Config Schema + +**Files:** +- Modify: `package.json` +- Modify: `pnpm-lock.yaml` +- Modify: `src/config.ts` + +- [ ] **Step 1: Update package dependencies** + +Run: + +```bash +pnpm remove pino pino-http pino-pretty +pnpm add winston +``` + +Expected: + +- `package.json` dependencies no longer include `pino` or `pino-http`. +- `package.json` devDependencies no longer include `pino-pretty`. +- `package.json` dependencies include `winston`. +- `pnpm-lock.yaml` updates accordingly. + +- [ ] **Step 2: Expand `LOG_LEVEL` validation** + +In `src/config.ts`, replace the current `LOG_LEVEL` line: + +```ts + LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), +``` + +with: + +```ts + LOG_LEVEL: z + .enum(["error", "warn", "info", "http", "verbose", "debug", "silly"]) + .default("info"), +``` + +- [ ] **Step 3: Run targeted config tests** + +Run: + +```bash +pnpm vitest run tests/config.test.ts +``` + +Expected: PASS. + +- [ ] **Step 4: Commit dependency and config changes** + +```bash +git add package.json pnpm-lock.yaml src/config.ts +git commit -m "chore: switch logging dependency to winston" +``` + +Expected: commit contains only dependency files and `src/config.ts`. + +--- + +### Task 3: Replace `src/logger.ts` With Winston Implementation + +**Files:** +- Modify: `src/logger.ts` + +- [ ] **Step 1: Replace logger implementation** + +Replace entire `src/logger.ts` with: + +```ts +import fs from "node:fs"; +import path from "node:path"; +import winston from "winston"; + +const isDev = process.env.NODE_ENV !== "production"; +const logLevel = process.env.LOG_LEVEL || (isDev ? "debug" : "info"); +const logsDir = path.resolve(process.cwd(), "logs"); + +fs.mkdirSync(logsDir, { recursive: true }); + +type LogMetadata = Record; + +type SerializedError = { + name: string; + message: string; + stack?: string; + code?: unknown; + statusCode?: unknown; +} & Record; + +const serializeError = (error: Error): SerializedError => { + const serialized: SerializedError = { + name: error.name, + message: error.message, + }; + + if (error.stack) { + serialized.stack = error.stack; + } + + const errorWithFields = error as Error & { + code?: unknown; + statusCode?: unknown; + [key: string]: unknown; + }; + + if (errorWithFields.code !== undefined) { + serialized.code = errorWithFields.code; + } + + if (errorWithFields.statusCode !== undefined) { + serialized.statusCode = errorWithFields.statusCode; + } + + for (const [key, value] of Object.entries(errorWithFields)) { + if (serialized[key] === undefined) { + serialized[key] = value; + } + } + + return serialized; +}; + +const serializeLogValue = (value: unknown): unknown => { + if (value instanceof Error) { + return serializeError(value); + } + + if (Array.isArray(value)) { + return value.map(serializeLogValue); + } + + if (value && typeof value === "object") { + return Object.fromEntries( + Object.entries(value).map(([key, nestedValue]) => [ + key, + serializeLogValue(nestedValue), + ]), + ); + } + + return value; +}; + +const formatLogMetadata = (metadata: LogMetadata): LogMetadata => { + return Object.fromEntries( + Object.entries(metadata).map(([key, value]) => [key, serializeLogValue(value)]), + ); +}; + +const metadataFormat = winston.format((info) => { + const { level, message, timestamp, ...metadata } = info; + return { + level, + message, + timestamp, + ...formatLogMetadata(metadata), + }; +}); + +const consoleFormat = winston.format.printf((info) => { + const { level, message, timestamp, context, ...metadata } = info; + const contextLabel = context ? ` [${String(context)}]` : ""; + const metadataText = Object.keys(metadata).length + ? ` ${JSON.stringify(metadata)}` + : ""; + + return `${timestamp} ${level}${contextLabel}: ${message}${metadataText}`; +}); + +export const logger = winston.createLogger({ + level: logLevel, + levels: winston.config.npm.levels, + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + metadataFormat(), + ), + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + metadataFormat(), + consoleFormat, + ), + }), + new winston.transports.File({ + filename: path.join(logsDir, "app.log"), + format: winston.format.json(), + }), + new winston.transports.File({ + filename: path.join(logsDir, "error.log"), + level: "error", + format: winston.format.json(), + }), + ], +}); + +export const createChildLogger = (context: string) => { + return logger.child({ context }); +}; + +export const serializeLogValueForTest = serializeLogValue; +export const formatLogMetadataForTest = formatLogMetadata; +``` + +- [ ] **Step 2: Run logger tests** + +Run: + +```bash +pnpm vitest run tests/logger.test.ts +``` + +Expected: PASS. + +- [ ] **Step 3: Run TypeScript check** + +Run: + +```bash +pnpm run typecheck +``` + +Expected: either PASS or type errors only from logger call sites needing Winston-compatible adjustments. + +- [ ] **Step 4: Commit Winston logger implementation** + +If typecheck passes: + +```bash +git add src/logger.ts +git commit -m "refactor: replace pino logger with winston" +``` + +Expected: commit contains only `src/logger.ts`. + +If typecheck fails in logger call sites, do not commit yet. Continue to Task 4, then commit `src/logger.ts` together with required call-site fixes. + +--- + +### Task 4: Fix Logger Call Sites Only If Needed + +**Files:** +- Inspect: `src/**/*.ts` +- Modify only files with TypeScript errors from Task 3. + +- [ ] **Step 1: Find logger imports and usages** + +Run: + +```bash +grep -rn "createChildLogger\|from \"./logger\"\|from \"../logger\"\|logger\.\|log\." src --include="*.ts" +``` + +Expected: list of logger imports and method calls. + +- [ ] **Step 2: Fix pino-style calls if TypeScript requires it** + +Use this mapping only where needed: + +Before: + +```ts +logger.info({ signal }, "Graceful shutdown initiated"); +``` + +After: + +```ts +logger.info("Graceful shutdown initiated", { signal }); +``` + +Before: + +```ts +logger.error({ error }, "Failed to initialize app"); +``` + +After: + +```ts +logger.error("Failed to initialize app", { error }); +``` + +Before: + +```ts +logger.warn({ error }, "Backlog sync failed"); +``` + +After: + +```ts +logger.warn("Backlog sync failed", { error }); +``` + +Plain string calls remain unchanged: + +```ts +logger.info("Creating Discord client"); +logger.debug("Queue empty, no playback started"); +``` + +- [ ] **Step 3: Re-run TypeScript check** + +Run: + +```bash +pnpm run typecheck +``` + +Expected: PASS. + +- [ ] **Step 4: Commit call-site fixes** + +```bash +git add src/logger.ts src/index.ts src/webserver.ts src/middleware.ts src/voiceController.ts src/media/mediaController.ts src/media/screenShareController.ts src/moderation/broadcaster.ts src/moderation/messageCapture.ts src/streaming/transcoder.ts +git commit -m "refactor: normalize logger call sites for winston" +``` + +Expected: commit stages only files actually changed. If some listed files are unchanged, `git add` is harmless. + +--- + +### Task 5: Ignore Runtime Log Files + +**Files:** +- Modify: `.gitignore` + +- [ ] **Step 1: Check whether `logs/` is already ignored** + +Run: + +```bash +grep -n "^logs/$" .gitignore || true +``` + +Expected: either one matching line or no output. + +- [ ] **Step 2: Add `logs/` if missing** + +If no output from Step 1, append: + +```gitignore +logs/ +``` + +- [ ] **Step 3: Verify ignore rule** + +Run: + +```bash +git check-ignore logs/app.log +``` + +Expected output: + +```text +logs/app.log +``` + +- [ ] **Step 4: Commit ignore rule** + +```bash +git add .gitignore +git commit -m "chore: ignore runtime log files" +``` + +Expected: commit contains only `.gitignore`. If `.gitignore` already ignored `logs/`, skip this commit. + +--- + +### Task 6: Verify No Pino Usage Remains + +**Files:** +- Inspect: repository source and package files + +- [ ] **Step 1: Search for Pino imports/usages** + +Run: + +```bash +grep -rn "pino\|pino-http\|pino-pretty" src package.json pnpm-lock.yaml --exclude-dir=node_modules || true +``` + +Expected: no output. If output appears only in old comments/docs outside runtime files, remove or update those comments/docs if they are part of the changed scope. + +- [ ] **Step 2: Search for Winston dependency** + +Run: + +```bash +grep -n '"winston"' package.json +``` + +Expected output contains one `winston` dependency line. + +- [ ] **Step 3: Run focused tests** + +Run: + +```bash +pnpm vitest run tests/logger.test.ts tests/config.test.ts +``` + +Expected: PASS. + +- [ ] **Step 4: Commit cleanup if any files changed** + +```bash +git status --short +git add +git commit -m "chore: remove remaining pino references" +``` + +Expected: commit only if Step 1 required changes. + +--- + +### Task 7: Runtime Log Output Verification + +**Files:** +- No source changes expected + +- [ ] **Step 1: Run one-shot logger command** + +Run: + +```bash +node --import tsx -e 'import { logger } from "./src/logger.ts"; logger.info("logger smoke info", { context: "smoke", value: 1 }); logger.error("logger smoke error", { error: new Error("smoke failure") }); await new Promise((resolve) => setTimeout(resolve, 250));' +``` + +Expected: + +- Console shows readable timestamped logs. +- Info line includes `smoke` context metadata. +- Error line includes serialized error metadata. + +- [ ] **Step 2: Verify app log file exists and contains JSON** + +Run: + +```bash +test -s logs/app.log && node -e 'const fs = require("fs"); const line = fs.readFileSync("logs/app.log", "utf8").trim().split("\n").at(-1); const parsed = JSON.parse(line); if (!parsed.level || !parsed.message) process.exit(1); console.log(parsed.level + ":" + parsed.message);' +``` + +Expected output includes: + +```text +error:logger smoke error +``` + +- [ ] **Step 3: Verify error log file exists and contains error JSON** + +Run: + +```bash +test -s logs/error.log && node -e 'const fs = require("fs"); const line = fs.readFileSync("logs/error.log", "utf8").trim().split("\n").at(-1); const parsed = JSON.parse(line); if (parsed.level !== "error") process.exit(1); console.log(parsed.level + ":" + parsed.message);' +``` + +Expected output: + +```text +error:logger smoke error +``` + +--- + +### Task 8: Full Validation + +**Files:** +- No source changes expected unless validation reveals issues. + +- [ ] **Step 1: Run typecheck** + +Run: + +```bash +pnpm run typecheck +``` + +Expected: PASS. + +- [ ] **Step 2: Run test suite** + +Run: + +```bash +pnpm run test +``` + +Expected: PASS. + +- [ ] **Step 3: Run lint** + +Run: + +```bash +pnpm run lint +``` + +Expected: PASS. + +- [ ] **Step 4: Inspect final git status** + +Run: + +```bash +git status --short +``` + +Expected: no uncommitted changes from this plan except ignored `logs/` files. Existing unrelated user changes may still appear; do not stage them. + +- [ ] **Step 5: Final commit if validation fixes were needed** + +If validation required fixes: + +```bash +git add +git commit -m "fix: stabilize winston logging migration" +``` + +Expected: commit contains only validation fixes. + +--- + +## Self-Review Notes + +- Spec coverage: dependency swap, `LOG_LEVEL`, centralized logger, console/file output, error serialization, Pino removal, and validation all have tasks. +- Placeholder scan: no TBD/TODO/fill-in placeholders remain; `` and similar are explicit safety instructions to avoid staging unrelated user changes. +- Type consistency: test helper names match `src/logger.ts` exports; Winston npm levels match `src/config.ts`; file names match spec. diff --git a/docs/superpowers/specs/2026-05-19-deprecated-dependency-removal-design.md b/docs/superpowers/specs/2026-05-19-deprecated-dependency-removal-design.md new file mode 100644 index 0000000..75d2682 --- /dev/null +++ b/docs/superpowers/specs/2026-05-19-deprecated-dependency-removal-design.md @@ -0,0 +1,91 @@ +# Deprecated Dependency Removal Design + +## Goal + +Remove deprecated packages from the pnpm lockfile where practical. Prefer maintained replacements or upgrades. If no maintained replacement exists, vendor upstream code as a submodule/workspace and patch dependency metadata there. + +## Scope + +Current deprecated sources: + +- `drizzle-kit` pulls `@esbuild-kit/esm-loader` and `@esbuild-kit/core-utils`. +- `discord.js-selfbot-v13` pulls `otplib@12` plugins. +- `@discordjs/opus` pulls `@discordjs/node-pre-gyp`, which pulls `npmlog`, `are-we-there-yet`, `gauge`, `rimraf@3`, `glob@7`, and `inflight`. +- `@lng2004/node-datachannel` and `better-sqlite3` pull `prebuild-install`. + +Existing workspace packages: + +- `vendor/discord.js-selfbot-v13` +- `vendor/discord-video-stream` + +## Approach + +1. Upgrade direct dependencies first and re-check `pnpm why` plus npm deprecation metadata. +2. Patch vendored workspace dependencies when project already owns package source. +3. Replace direct packages only when runtime compatibility is clear. +4. Add submodules only for packages that cannot be replaced or upgraded without keeping deprecated transitive packages. + +## Package Plan + +### `drizzle-kit` + +Try latest compatible `drizzle-kit`. If latest still depends on `@esbuild-kit/*`, keep current version unless project commands fail, because vendoring `drizzle-kit` only to remove dev-only install warnings has high maintenance cost. + +### `discord.js-selfbot-v13` + +Patch `vendor/discord.js-selfbot-v13` dependency graph to remove `otplib@12` if code is compatible with `otplib@13`. Verify by installing and running typecheck/tests. Keep peer/package name unchanged. + +### `@discordjs/opus` + +Find maintained Opus alternative compatible with current recorder and `@discordjs/voice`. Prefer removing direct `@discordjs/opus` only if code and tests still pass. If native Opus remains needed and every maintained option drags deprecated install tooling, vendor the smallest dependency owner. + +### `prebuild-install` sources + +Do not patch native package install chains blindly. For `better-sqlite3`, keep upstream unless latest removes `prebuild-install`. For `@lng2004/node-datachannel`, try latest first through `discord-video-stream`; vendor only if strict lockfile cleanup remains blocked and build still works. + +### `discord-video-stream` + +Keep as workspace submodule. Patch devDependency `discord.js-selfbot-v13` to use workspace reference so installs do not fetch deprecated registry selfbot. + +## Verification + +After each dependency change: + +1. Run `pnpm install`. +2. Run `pnpm why` for known deprecated package names. +3. Check npm deprecation metadata for remaining lockfile packages. +4. Run `pnpm run typecheck`. +5. Run `pnpm run test`. + +## Success Criteria + +- Root `package.json` uses workspace paths for vendored packages. +- `pnpm-lock.yaml` has no deprecated packages where maintained replacements exist. +- Any remaining deprecated packages are documented as no-maintained-replacement and owned by a vendored submodule or unavoidable native upstream. +- Typecheck and tests pass. + +## Final Audit Result + +Deprecated packages removed from active dependency graph: + +- `@otplib/plugin-crypto`, `@otplib/plugin-thirty-two`, `@otplib/preset-default` — removed by patching `vendor/discord.js-selfbot-v13` to `otplib@13`. +- `@discordjs/opus` direct dependency — removed from root dependencies. +- `@discordjs/node-pre-gyp`, `npmlog`, `are-we-there-yet`, `gauge`, `rimraf@3`, `glob@7`, `inflight` — removed by eliminating `@discordjs/opus` auto-installed peer path. + +Remaining unavoidable deprecated packages: + +- `@esbuild-kit/core-utils@3.3.2` via `drizzle-kit@0.31.10`. +- `@esbuild-kit/esm-loader@2.6.5` via `drizzle-kit@0.31.10`. +- `prebuild-install@7.1.3` via `better-sqlite3@12.10.0` and `@lng2004/node-datachannel@0.32.0-20260202`. + +Reason these remain: + +- `drizzle-kit@0.31.10` is latest stable and still depends on `@esbuild-kit/*`. +- `better-sqlite3@12.10.0` is latest stable and still uses `prebuild-install` for native binary install. +- `@lng2004/node-datachannel@0.32.0-20260202` is latest available and still uses `prebuild-install` for native binary install. + +The upstream repositories are vendored as submodules for future patching if strict zero-deprecated lockfile becomes worth maintaining as forks: + +- `vendor/drizzle-orm` +- `vendor/better-sqlite3` +- `vendor/node-datachannel` diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 41c7d6f..e22c300 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ packages: - . + - vendor/discord-video-stream - vendor/discord.js-selfbot-v13 onlyBuiltDependencies: diff --git a/src/moderation/llmModerationClient.ts b/src/moderation/llmModerationClient.ts index 5229601..cfe535e 100644 --- a/src/moderation/llmModerationClient.ts +++ b/src/moderation/llmModerationClient.ts @@ -91,15 +91,16 @@ export function parseModerationResponse( message_id: msgId, status: (parsed as any).status || "clean", flags: (parsed as any).flags || [], - score: (parsed as any).score !== undefined ? (parsed as any).score : 0.1, + score: + (parsed as any).score !== undefined ? (parsed as any).score : 0.1, analysis: (parsed as any).analysis || "", }, ], }; } else { // Look for any array property (result, data, messages, moderation, etc.) - const arrayKey = Object.keys(parsed).find( - (key) => Array.isArray((parsed as any)[key]), + const arrayKey = Object.keys(parsed).find((key) => + Array.isArray((parsed as any)[key]), ); if (arrayKey) { parsed.results = (parsed as any)[arrayKey]; @@ -537,7 +538,8 @@ Return ONLY valid JSON, no other text.`; try { parsed = parseModerationResponse(content, targetIds); } catch (parseError) { - const errorMsg = parseError instanceof Error ? parseError.message : String(parseError); + const errorMsg = + parseError instanceof Error ? parseError.message : String(parseError); log.error( { error: errorMsg, diff --git a/src/routes/recordingsRoutes.ts b/src/routes/recordingsRoutes.ts index edd5ab0..31df3ef 100644 --- a/src/routes/recordingsRoutes.ts +++ b/src/routes/recordingsRoutes.ts @@ -1,8 +1,8 @@ import { - Router, + type NextFunction, type Request, type Response, - type NextFunction, + Router, } from "express"; import { listVoiceRecordings } from "../database/voiceRecordingRepo"; import { AppError } from "../errors"; diff --git a/src/webserver.ts b/src/webserver.ts index fbfbd6c..8614e46 100644 --- a/src/webserver.ts +++ b/src/webserver.ts @@ -25,10 +25,10 @@ import { discordPlayer } from "./player"; import { createAnalysisRoutes } from "./routes/analysisRoutes"; import { createMediaRoutes } from "./routes/mediaRoutes"; import { createMessageRoutes } from "./routes/messageRoutes"; +import { createRecordingsRoutes } from "./routes/recordingsRoutes"; import { createSyncRoutes } from "./routes/syncRoutes"; import { createUIStateRoutes } from "./routes/uiStateRoutes"; import { createVoiceRoutes } from "./routes/voiceRoutes"; -import { createRecordingsRoutes } from "./routes/recordingsRoutes"; import { Streamer } from "./streaming"; import type { VoiceController } from "./voiceController"; diff --git a/tests/moderation/llmModerationClient.test.ts b/tests/moderation/llmModerationClient.test.ts index 72c3c3d..2e87979 100644 --- a/tests/moderation/llmModerationClient.test.ts +++ b/tests/moderation/llmModerationClient.test.ts @@ -415,7 +415,9 @@ describe("runModerationAnalysis", () => { expect(requestBody.temperature).toBe(0); expect(requestBody.response_format).toEqual({ type: "json_object" }); expect(requestBody.reasoning_budget).toBeUndefined(); - expect(requestBody.chat_template_kwargs).toEqual({ enable_thinking: false }); + expect(requestBody.chat_template_kwargs).toEqual({ + enable_thinking: false, + }); }); it("throws on non-ok HTTP response", async () => { @@ -655,7 +657,9 @@ describe("runModerationAnalysis", () => { expect(downloadedUrls).toContain("https://httpbin.org/image/png?source=t2"); expect(downloadedUrls).toContain("https://httpbin.org/image/png?source=t1"); expect(downloadedUrls).toContain("https://httpbin.org/image/png?source=c7"); - expect(downloadedUrls).not.toContain("https://httpbin.org/image/png?source=c1"); + expect(downloadedUrls).not.toContain( + "https://httpbin.org/image/png?source=c1", + ); }); it("sends verified real PNG and JPEG attachments with realistic Indonesian text", async () => { @@ -761,7 +765,9 @@ describe("runModerationAnalysis", () => { const requestBody = JSON.parse(fetchCalls[2][1].body); const contentParts = requestBody.messages[0].content; - expect(contentParts.filter((part: any) => part.type === "image_url")).toHaveLength(2); + expect( + contentParts.filter((part: any) => part.type === "image_url"), + ).toHaveLength(2); expect(contentParts[0].image_url.url).toContain("data:image/jpeg;base64,"); expect(contentParts[2].image_url.url).toContain("data:image/png;base64,"); expect(contentParts.at(-1).text).toContain("https://example.invalid/login"); @@ -795,7 +801,10 @@ describe("runModerationAnalysis", () => { return Promise.resolve({ ok: true, arrayBuffer: async () => - buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), + buffer.buffer.slice( + buffer.byteOffset, + buffer.byteOffset + buffer.byteLength, + ), }); } @@ -807,7 +816,12 @@ describe("runModerationAnalysis", () => { }); await runModerationAnalysis({ - targets: [createMessageRecord({ id: "m1", content: "gambar belum selesai upload ke picser" })], + targets: [ + createMessageRecord({ + id: "m1", + content: "gambar belum selesai upload ke picser", + }), + ], contextText: "test context", attachments: [ { @@ -830,7 +844,9 @@ describe("runModerationAnalysis", () => { ], }); - expect((global.fetch as any).mock.calls[0][0]).toBe("https://httpbin.org/image/png"); + expect((global.fetch as any).mock.calls[0][0]).toBe( + "https://httpbin.org/image/png", + ); }); it("keeps analyzing text when an image URL returns non-OK", async () => { @@ -845,7 +861,8 @@ describe("runModerationAnalysis", () => { status: "warn", flags: ["suspicious_link"], score: 0.6, - analysis: "Image fetch failed, text still indicates suspicious link.", + analysis: + "Image fetch failed, text still indicates suspicious link.", }, ], }), @@ -873,7 +890,8 @@ describe("runModerationAnalysis", () => { content: "cek bonus gratis di https://example.invalid/claim sekarang", }), ], - contextText: "Pesan ini dikirim berulang setelah user lain menolak klik link.", + contextText: + "Pesan ini dikirim berulang setelah user lain menolak klik link.", attachments: [ { id: "bad-image", @@ -899,7 +917,9 @@ describe("runModerationAnalysis", () => { const requestBody = JSON.parse((global.fetch as any).mock.calls[1][1].body); expect(requestBody.messages[0].content).toHaveLength(1); - expect(requestBody.messages[0].content[0].text).toContain("https://example.invalid/claim"); + expect(requestBody.messages[0].content[0].text).toContain( + "https://example.invalid/claim", + ); }); describe("Edge Cases & Real-World Scenarios", () => { @@ -1349,9 +1369,7 @@ describe("runModerationAnalysis", () => { ] }`; - expect(() => - parseModerationResponse(content, ["m1"]), - ).toThrow(/finite/i); + expect(() => parseModerationResponse(content, ["m1"])).toThrow(/finite/i); }); }); }); diff --git a/vendor/better-sqlite3 b/vendor/better-sqlite3 index d8885f9..7fa2365 160000 --- a/vendor/better-sqlite3 +++ b/vendor/better-sqlite3 @@ -1 +1 @@ -Subproject commit d8885f900cb626596e28a0ecd1b9d35bf15c7a0b +Subproject commit 7fa236543297bb3e01d84f8fcf0b8a068d6bc9ab diff --git a/vendor/discord-video-stream b/vendor/discord-video-stream index 2312e75..ba91cc1 160000 --- a/vendor/discord-video-stream +++ b/vendor/discord-video-stream @@ -1 +1 @@ -Subproject commit 2312e759530ae06ed3595115b9c3426f462c8a34 +Subproject commit ba91cc1b2f451851c3f104ddc168fd6391eacd86 diff --git a/vendor/drizzle-orm b/vendor/drizzle-orm index 48e5406..c5d4f0c 160000 --- a/vendor/drizzle-orm +++ b/vendor/drizzle-orm @@ -1 +1 @@ -Subproject commit 48e5406027103a9fca6eb66417187c4a8b5c6aa3 +Subproject commit c5d4f0c4a742479ed78785e317ed4857311300f0 diff --git a/vendor/node-datachannel b/vendor/node-datachannel index 46c65c8..29e898b 160000 --- a/vendor/node-datachannel +++ b/vendor/node-datachannel @@ -1 +1 @@ -Subproject commit 46c65c88b7f76faf8fbece948e174e67b305ca04 +Subproject commit 29e898be59be3957cbd23ef6caa9ab5b0303a725