CLose story 1.2
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
# Blind Hunter Review Layer
|
||||
|
||||
## ROLE
|
||||
You are **Blind Hunter** — an adversarial code reviewer. You have NO access to the project, spec, or any context. You only see the diff below.
|
||||
|
||||
## MISSION
|
||||
Find problems. Be ruthless. Assume nothing is intentional. Look for:
|
||||
- **Security vulnerabilities** (injection, XSS, path traversal, hardcoded secrets)
|
||||
- **Bugs** (logical errors, race conditions, null dereferences)
|
||||
- **Performance issues** (N+1 queries, unnecessary computations, memory leaks)
|
||||
- **Anti-patterns** (god objects, circular dependencies, mutable globals)
|
||||
- **Code smells** (duplicate code, long methods, magic numbers)
|
||||
- **Best practice violations** (error handling, input validation, coding standards)
|
||||
- **Anything suspicious** (unusual patterns, odd dependencies, weird configurations)
|
||||
|
||||
## OUTPUT FORMAT
|
||||
Output ONLY a Markdown list of findings. No preamble, no summary. Each finding:
|
||||
```markdown
|
||||
- **[SEVERITY]** Short title — file:line — evidence/quote from diff
|
||||
```
|
||||
Severity: CRITICAL, HIGH, MEDIUM, LOW, INFO
|
||||
|
||||
## DIFF TO REVIEW
|
||||
|
||||
```diff
|
||||
diff --git a/.gitignore b/.gitignore
|
||||
new file mode 100644
|
||||
index 0000000..c2ccb26
|
||||
--- /dev/null
|
||||
+++ b/.gitignore
|
||||
@@ -0,0 +1,4 @@
|
||||
+dist/
|
||||
+node_modules/
|
||||
+*.zip
|
||||
+*.lock
|
||||
diff --git a/eslint.config.js b/eslint.config.js
|
||||
new file mode 100644
|
||||
index 0000000..4de7904
|
||||
--- /dev/null
|
||||
+++ b/eslint.config.js
|
||||
@@ -0,0 +1,124 @@
|
||||
+import js from "@eslint/js";
|
||||
+import jsdoc from "eslint-plugin-jsdoc";
|
||||
+import importPlugin from "eslint-plugin-import";
|
||||
+import globals from "globals";
|
||||
+import { fileURLToPath } from "url";
|
||||
+import { dirname } from "path";
|
||||
+
|
||||
+const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
+
|
||||
+export default [
|
||||
+ js.configs.recommended,
|
||||
+ {
|
||||
+ plugins: {
|
||||
+ jsdoc,
|
||||
+ import: importPlugin,
|
||||
+ },
|
||||
+ languageOptions: {
|
||||
+ globals: {
|
||||
+ // Browser built-ins (console, setTimeout, etc.)
|
||||
+ ...globals.browser,
|
||||
+ // FoundryVTT globals injected at runtime
|
||||
+ Hooks: "readonly",
|
||||
+ game: "readonly",
|
||||
+ ui: "readonly",
|
||||
+ canvas: "readonly",
|
||||
+ foundry: "readonly",
|
||||
+ CONFIG: "readonly",
|
||||
+ CONST: "readonly",
|
||||
+ },
|
||||
+ },
|
||||
+ rules: {
|
||||
+ // Require JSDoc on all exported symbols
|
||||
+ "jsdoc/require-jsdoc": [
|
||||
+ "error",
|
||||
+ {
|
||||
+ publicOnly: true,
|
||||
+ require: {
|
||||
+ FunctionDeclaration: true,
|
||||
+ MethodDefinition: true,
|
||||
+ ClassDeclaration: true,
|
||||
+ ArrowFunctionExpression: false,
|
||||
+ FunctionExpression: false,
|
||||
+ },
|
||||
+ contexts: ["ExportNamedDeclaration > FunctionDeclaration"],
|
||||
+ },
|
||||
+ ],
|
||||
+ "jsdoc/require-param": "warn",
|
||||
+ "jsdoc/require-returns": "warn",
|
||||
+
|
||||
+ // Import boundary enforcement
|
||||
+ "import/no-restricted-paths": [
|
||||
+ "error",
|
||||
+ {
|
||||
+ zones: [
|
||||
+ // src/core/ → may import src/contracts/ and src/utils/ ONLY
|
||||
+ {
|
||||
+ target: "./src/core",
|
||||
+ from: "./src/foundry",
|
||||
+ message: "src/core/ must not import from src/foundry/",
|
||||
+ },
|
||||
+ {
|
||||
+ target: "./src/core",
|
||||
+ from: "./src/ui",
|
||||
+ message: "src/core/ must not import from src/ui/",
|
||||
+ },
|
||||
+ {
|
||||
+ target: "./src/core",
|
||||
+ from: "./src/notifications",
|
||||
+ message: "src/core/ must not import from src/notifications/",
|
||||
+ },
|
||||
+ {
|
||||
+ target: "./src/core",
|
||||
+ from: "./src/presets",
|
||||
+ message: "src/core/ must not import from src/presets/",
|
||||
+ },
|
||||
+ // src/foundry/ → may import src/contracts/ and src/utils/ ONLY
|
||||
+ {
|
||||
+ target: "./src/foundry",
|
||||
+ from: "./src/core",
|
||||
+ message: "src/foundry/ must not import from src/core/",
|
||||
+ },
|
||||
+ {
|
||||
+ target: "./src/foundry",
|
||||
+ from: "./src/ui",
|
||||
+ message: "src/foundry/ must not import from src/ui/",
|
||||
+ },
|
||||
+ {
|
||||
+ target: "./src/foundry",
|
||||
+ from: "./src/notifications",
|
||||
+ message: "src/foundry/ must not import from src/notifications/",
|
||||
+ },
|
||||
+ {
|
||||
+ target: "./src/foundry",
|
||||
+ from: "./src/presets",
|
||||
+ message: "src/foundry/ must not import from src/presets/",
|
||||
+ },
|
||||
+ // src/contracts/ → no internal imports
|
||||
+ {
|
||||
+ target: "./src/contracts",
|
||||
+ from: "./src",
|
||||
+ message: "src/contracts/ must not import from other src/ modules",
|
||||
+ },
|
||||
+ // src/utils/ → no internal imports
|
||||
+ {
|
||||
+ target: "./src/utils",
|
||||
+ from: "./src",
|
||||
+ message: "src/utils/ must not import from other src/ modules",
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ files: ["tests/**/*.js"],
|
||||
+ rules: {
|
||||
+ // Relax JSDoc requirement for test files
|
||||
+ "jsdoc/require-jsdoc": "off",
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ ignores: ["dist/", "node_modules/", "*.zip"],
|
||||
+ },
|
||||
+];
|
||||
diff --git a/module.js b/module.js
|
||||
new file mode 100644
|
||||
index 0000000..a3ad2d7
|
||||
--- /dev/null
|
||||
+++ b/module.js
|
||||
@@ -0,0 +1,24 @@
|
||||
+/**
|
||||
+ * module.js — Entry point and wiring diagram for Video View Manager (Scrying Pool).
|
||||
+ *
|
||||
+ * This file is the wiring diagram ONLY. It imports all modules, constructs them
|
||||
+ * with injected dependencies, and holds NO business logic.
|
||||
+ *
|
||||
+ * Initialisation order:
|
||||
+ * Hooks.once('init') → register world settings → construct FoundryAdapter
|
||||
+ * → StateStore → SocketHandler (queue+drain)
|
||||
+ * Hooks.once('ready') → VisibilityManager → SocketHandler.setReady()
|
||||
+ * → NotificationBus → RoleRenderer → RosterStrip
|
||||
+ * → DirectorsBoard (lazy, GM only)
|
||||
+ */
|
||||
+
|
||||
+Hooks.once("init", () => {
|
||||
+ console.log("[ScryingPool] init — module loading");
|
||||
+ // Story 1.3+: register world settings, construct FoundryAdapter, StateStore, SocketHandler
|
||||
+});
|
||||
+
|
||||
+Hooks.once("ready", () => {
|
||||
+ console.log("[ScryingPool] ready — module active");
|
||||
+ // Story 1.3+: construct VisibilityManager, NotificationBus, RoleRenderer, RosterStrip
|
||||
+ // Story 1.5+: register DirectorsBoard (lazy, GM only)
|
||||
+});
|
||||
diff --git a/module.json b/module.json
|
||||
new file mode 100644
|
||||
index 0000000..e1cffdc
|
||||
--- /dev/null
|
||||
+++ b/module.json
|
||||
@@ -0,0 +1,29 @@
|
||||
+{
|
||||
+ "id": "video-view-manager",
|
||||
+ "title": "Video View Manager (Scrying Pool)",
|
||||
+ "version": "0.1.0",
|
||||
+ "description": "GM camera visibility control for FoundryVTT v14 — hide, show, and manage participant feeds in real time.",
|
||||
+ "authors": [
|
||||
+ {
|
||||
+ "name": "Morr"
|
||||
+ }
|
||||
+ ],
|
||||
+ "compatibility": {
|
||||
+ "minimum": "14",
|
||||
+ "verified": "14"
|
||||
+ },
|
||||
+ "esmodules": [
|
||||
+ "module.js"
|
||||
+ ],
|
||||
+ "styles": [
|
||||
+ "dist/styles/scrying-pool.css"
|
||||
+ ],
|
||||
+ "languages": [
|
||||
+ {
|
||||
+ "lang": "en",
|
||||
+ "name": "English",
|
||||
+ "path": "lang/en.json"
|
||||
+ }
|
||||
+ ],
|
||||
+ "flags": {}
|
||||
+}
|
||||
diff --git a/package.json b/package.json
|
||||
new file mode 100644
|
||||
index 0000000..fab7015
|
||||
--- /dev/null
|
||||
+++ b/package.json
|
||||
@@ -0,0 +1,26 @@
|
||||
+{
|
||||
+ "name": "video-view-manager",
|
||||
+ "version": "0.1.0",
|
||||
+ "description": "FoundryVTT v14 module — Scrying Pool camera visibility control",
|
||||
+ "type": "module",
|
||||
+ "scripts": {
|
||||
+ "build": "lessc styles/scrying-pool.less dist/styles/scrying-pool.css",
|
||||
+ "watch": "chokidar 'styles/**/*.less' -c 'lessc styles/scrying-pool.less dist/styles/scrying-pool.css'",
|
||||
+ "typecheck": "tsc --noEmit",
|
||||
+ "lint": "eslint src/ module.js",
|
||||
+ "test": "vitest run",
|
||||
+ "test:watch": "vitest",
|
||||
+ "release": "node scripts/package.mjs"
|
||||
+ },
|
||||
+ "devDependencies": {
|
||||
+ "@types/node": "^22.0.0",
|
||||
+ "chokidar": "5.0.0",
|
||||
+ "eslint": "^9.0.0",
|
||||
+ "eslint-plugin-import": "^2.31.0",
|
||||
+ "eslint-plugin-jsdoc": "^50.0.0",
|
||||
+ "happy-dom": "^20.0.0",
|
||||
+ "less": "4.6.4",
|
||||
+ "typescript": "5.9.3",
|
||||
+ "vitest": "2.1.8"
|
||||
+ }
|
||||
+}
|
||||
diff --git a/scripts/package.mjs b/scripts/package.mjs
|
||||
new file mode 100644
|
||||
index 0000000..9a7300e
|
||||
--- /dev/null
|
||||
+++ b/scripts/package.mjs
|
||||
@@ -0,0 +1,60 @@
|
||||
+/**
|
||||
+ * Release script — produces module.zip.
|
||||
+ *
|
||||
+ * Single version source of truth: reads version from package.json,
|
||||
+ * writes it into module.json, then zips all release artefacts.
|
||||
+ *
|
||||
+ * Usage: node scripts/package.mjs
|
||||
+ */
|
||||
+
|
||||
+import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
+import { resolve, dirname } from "path";
|
||||
+import { fileURLToPath } from "url";
|
||||
+import { createGzip } from "zlib";
|
||||
+import { exec } from "child_process";
|
||||
+import { promisify } from "util";
|
||||
+
|
||||
+const execAsync = promisify(exec);
|
||||
+const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
+const ROOT = resolve(__dirname, "..");
|
||||
+
|
||||
+// Read version from package.json (single source of truth)
|
||||
+const pkg = JSON.parse(readFileSync(resolve(ROOT, "package.json"), "utf8"));
|
||||
+const { version } = pkg;
|
||||
+
|
||||
+// Write version into module.json
|
||||
+const moduleJsonPath = resolve(ROOT, "module.json");
|
||||
+const moduleJson = JSON.parse(readFileSync(moduleJsonPath, "utf8"));
|
||||
+moduleJson.version = version;
|
||||
+writeFileSync(moduleJsonPath, JSON.stringify(moduleJson, null, 2) + "\n", "utf8");
|
||||
+console.log(`[ScryingPool] module.json version set to ${version}`);
|
||||
+
|
||||
+// Ensure dist/ exists (build should have run first)
|
||||
+if (!existsSync(resolve(ROOT, "dist"))) {
|
||||
+ console.error("[ScryingPool] dist/ not found — run npm run build first");
|
||||
+ process.exit(1);
|
||||
+}
|
||||
+
|
||||
+// Files and directories to include in module.zip
|
||||
+const INCLUDE = [
|
||||
+ "module.json",
|
||||
+ "module.js",
|
||||
+ "lang/",
|
||||
+ "templates/",
|
||||
+ "dist/",
|
||||
+ "src/",
|
||||
+];
|
||||
+
|
||||
+// Build zip using system zip command
|
||||
+const targets = INCLUDE.filter((f) => existsSync(resolve(ROOT, f)));
|
||||
+const zipArgs = targets.map((t) => `"${t}"`).join(" ");
|
||||
+const zipCmd = `cd "${ROOT}" && zip -r module.zip ${zipArgs}`;
|
||||
+
|
||||
+console.log("[ScryingPool] Creating module.zip...");
|
||||
+try {
|
||||
+ await execAsync(zipCmd);
|
||||
+ console.log(`[ScryingPool] module.zip created (v${version})`);
|
||||
+} catch (err) {
|
||||
+ console.error("[ScryingPool] zip failed:", err instanceof Error ? err.message : String(err));
|
||||
+ process.exit(1);
|
||||
+}
|
||||
diff --git a/tsconfig.json b/tsconfig.json
|
||||
new file mode 100644
|
||||
index 0000000..7d64af8
|
||||
--- /dev/null
|
||||
+++ b/tsconfig.json
|
||||
@@ -0,0 +1,14 @@
|
||||
+{
|
||||
+ "compilerOptions": {
|
||||
+ "checkJs": true,
|
||||
+ "strict": true,
|
||||
+ "noEmit": true,
|
||||
+ "target": "ESNext",
|
||||
+ "module": "ESNext",
|
||||
+ "moduleResolution": "bundler",
|
||||
+ "allowJs": true,
|
||||
+ "lib": ["ESNext", "DOM"]
|
||||
+ },
|
||||
+ "include": ["src/**/*.js", "src/**/*.d.ts", "module.js", "scripts/**/*.mjs", "tests/**/*.js"],
|
||||
+ "exclude": ["node_modules", "dist"]
|
||||
+}
|
||||
diff --git a/vitest.config.js b/vitest.config.js
|
||||
new file mode 100644
|
||||
index 0000000..c18efc0
|
||||
--- /dev/null
|
||||
+++ b/vitest.config.js
|
||||
@@ -0,0 +1,23 @@
|
||||
+import { defineConfig } from "vitest/config";
|
||||
+
|
||||
+export default defineConfig({
|
||||
+ test: {
|
||||
+ environment: "happy-dom",
|
||||
+ globals: false,
|
||||
+ include: ["tests/**/*.test.js"],
|
||||
+ coverage: {
|
||||
+ provider: "v8",
|
||||
+ reporter: ["text", "lcov"],
|
||||
+ include: ["src/**/*.js"],
|
||||
+ exclude: ["src/contracts/**"],
|
||||
+ },
|
||||
+ },
|
||||
+ resolve: {
|
||||
+ alias: {
|
||||
+ "@src": "/src",
|
||||
+ "@contracts": "/src/contracts",
|
||||
+ "@utils": "/src/utils",
|
||||
+ "@tests": "/tests",
|
||||
+ },
|
||||
+ },
|
||||
+});
|
||||
Reference in New Issue
Block a user