From bfe1e5cb51b711313f49dee3f6e702a79a9e2bf7 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Mon, 3 Aug 2026 05:08:02 -0400 Subject: [PATCH 01/15] feat(ci): enforce main branch PRs originate from dev (part of web-jam-tools#351) --- .circleci/config.yml | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 43c1695..575828a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,16 @@ jobs: working_directory: ~/repo steps: - checkout + - run: + name: PR base branch verification (main requires dev head) + command: | + TARGET="${CIRCLE_TARGET_BRANCH:-${GITHUB_BASE_REF:-}}" + SOURCE="${CIRCLE_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}" + if [ -n "$TARGET" ] && [ "$TARGET" = "main" ] && [ "$SOURCE" != "dev" ]; then + echo "FAIL: PRs targeting main must originate from dev branch (got: $SOURCE)." >&2 + exit 1 + fi + echo "OK: PR base/head branch verification passed (Target: ${TARGET:-default}, Source: $SOURCE)" - run: name: Test install command: 'npm install --ignore-scripts' diff --git a/package.json b/package.json index c4e064b..b6b746f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.15", + "version": "3.0.16", "license": "MIT", "type": "module", "main": "build/src/index.js", From 654c0ab2cfd7dd1389257d93aced803b3c629c9a Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Mon, 3 Aug 2026 05:33:25 -0400 Subject: [PATCH 02/15] fix(deps): npm audit fix and update AGENTS.md with Snyk failure notes --- AGENTS.md | 3 +++ package-lock.json | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a5e43d9..d1bdd39 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,3 +5,6 @@ - **Shared Domain Types**: Centralized types for socket connections, streams, payloads, and domain objects live in `src/types/index.ts`. - **Mongoose Generic Facade**: Model facades extend `Facade` defined in `src/lib/facade.ts`. When typing generic model methods, use double type assertions (e.g. `(await ... as unknown) as T[]`) to satisfy Mongoose generic method signatures without using `any`. - **SocketCluster Mocks**: When mocking `AGServer` or `IClient` in tests, cast stub objects using `as unknown as socketClusterServer.AGServer` or `as unknown as IClient`. Ensure `receiver.next()` mocks return `{ value?: T; done?: boolean }`. + +## Memory & Security Audits +- **Snyk Failures & Resolution via `npm audit fix`**: PR checks may report failure on `security/snyk` due to transitive dependency vulnerabilities. Running `npm audit fix` updates `package-lock.json` with non-breaking patches to resolve these vulnerabilities. Always run local tests afterwards to verify the test suite remains 100% green before committing and pushing `package-lock.json` to the PR branch. diff --git a/package-lock.json b/package-lock.json index 140794b..2d1d728 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.15", + "version": "3.0.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.15", + "version": "3.0.16", "hasInstallScript": true, "license": "MIT", "dependencies": { From 3e8784fcf2deb9f2d3f3504004a46a5bf7054ce5 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Mon, 3 Aug 2026 06:08:55 -0400 Subject: [PATCH 03/15] feat(ci): bump Node.js to 24.18.1 in engines and CircleCI --- .circleci/config.yml | 4 ++-- .nvmrc | 2 +- package.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 575828a..38858bb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,8 +2,8 @@ version: 2.1 jobs: build: docker: - # Node 24.18 (engines/.nvmrc) — matching browsers image. - - image: cimg/node:24.18-browsers + # Node 24.18.1 (engines/.nvmrc) — matching browsers image. + - image: cimg/node:24.18.1-browsers working_directory: ~/repo steps: - checkout diff --git a/.nvmrc b/.nvmrc index ca5c350..8dfc5cb 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.18.0 +24.18.1 diff --git a/package.json b/package.json index b6b746f..d3def2b 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.16", + "version": "3.0.17", "license": "MIT", "type": "module", "main": "build/src/index.js", "engines": { - "node": "24.18.0" + "node": "24.18.1" }, "contributors": [ { From 5c3ddd80f1005c1b061201328fe19385e6dfe7a6 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Mon, 3 Aug 2026 06:19:37 -0400 Subject: [PATCH 04/15] docs: update AGENTS.md with Node.js v24.18.1 and keep cimg/node:24.18-browsers tag --- .circleci/config.yml | 4 ++-- AGENTS.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 38858bb..575828a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,8 +2,8 @@ version: 2.1 jobs: build: docker: - # Node 24.18.1 (engines/.nvmrc) — matching browsers image. - - image: cimg/node:24.18.1-browsers + # Node 24.18 (engines/.nvmrc) — matching browsers image. + - image: cimg/node:24.18-browsers working_directory: ~/repo steps: - checkout diff --git a/AGENTS.md b/AGENTS.md index d1bdd39..380d27c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,3 +8,7 @@ ## Memory & Security Audits - **Snyk Failures & Resolution via `npm audit fix`**: PR checks may report failure on `security/snyk` due to transitive dependency vulnerabilities. Running `npm audit fix` updates `package-lock.json` with non-breaking patches to resolve these vulnerabilities. Always run local tests afterwards to verify the test suite remains 100% green before committing and pushing `package-lock.json` to the PR branch. + +## System & Environment +- **Runtime:** Node.js v24.18.1 + From 49bab092e5433bb62ad77b527997201b6b970297 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Mon, 3 Aug 2026 06:24:53 -0400 Subject: [PATCH 05/15] feat: update package-lock.json engines node --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d1d728..8c40fa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.16", + "version": "3.0.17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.16", + "version": "3.0.17", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -53,7 +53,7 @@ "vitest": "^4.1.9" }, "engines": { - "node": "24.18.0" + "node": "24.18.1" } }, "node_modules/@babel/helper-string-parser": { From 3ec083ecf974452d3a3a6a678dc6637147197a64 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Mon, 3 Aug 2026 06:25:21 -0400 Subject: [PATCH 06/15] docs: update AGENTS.md rule to specify --ignore-scripts --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 380d27c..1120aff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,4 +11,5 @@ ## System & Environment - **Runtime:** Node.js v24.18.1 +- **Node Engine Version Bumps:** When bumping Node.js in `package.json` `engines.node`, run `npm install --package-lock-only --ignore-scripts` (or `npm install --ignore-scripts`) to update `package-lock.json` root engine definition without waiting on `postinstall` scripts so both files are committed together. From 7c1c3c01e20380ad39008022ab95a24948609574 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Tue, 4 Aug 2026 16:41:46 -0400 Subject: [PATCH 07/15] feat: sync canonical cross-AI rules into AGENTS.md --- AGENTS.md | 26 +++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1120aff..e4a7814 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,30 @@ # Repository Guidelines for WebJamSocketCluster + + +## OPERATIONAL HARD RULES (apply to any AI taking action on Josh's behalf) + +- CALENDAR CONFLICT: never schedule over an existing event without Josh's explicit override. +- EMAIL: always DRAFT, never send. Save as Gmail draft for Josh's review. +- FILES: never create a version-suffixed copy. Edit the master. +- Never contact venues, churches, or other third parties directly — Josh handles all outreach. +- **STATE VERIFICATION**: Before any suggestion, to-do item, or "ready for you" claim about a PR/issue/CI/deploy, run a fresh liveness check in that same turn (e.g. `gh pr view --json state,mergedAt` / `gh issue view --json state`). If state ≠ OPEN, it is done: drop it silently. `mergeable: UNKNOWN/null` on a PR usually means merged/closed — never read it as "the API is slow" and never advise merging without confirming state=OPEN. An inconclusive check is not a completed check: use a definitive fallback (local `git merge-tree`, `statusCheckRollup`) or say plainly that you could not verify — never hand Josh a verification step the agent can run itself. +- **ONE REPO, ONE SESSION**: never edit a repo another AI session is actively working (Josh, 2026-07-11). Before branching or editing, check `git status -sb` — a non-`dev` branch or dirty tree means another session likely has the repo in flight. Hand the change to that session/lane (route via Josh) or ask Josh first. A separate worktree or non-colliding branch does NOT make concurrent edits OK — parallel semver bumps and surprise PRs still collide. +- **MAX 2 CONCURRENT WORKSTREAMS PER TERMINAL**: Two live background jobs (e.g. a subagent + a headless agy dispatch) is the cap. When a THIRD thread (new discussion, dispatch, or background job) starts in the same session, the agent must WARN Josh first and propose a separate terminal — never comply silently. Origin: 2026-07-16, Claude A froze mid-permission-prompt while running a Sonnet subagent + a headless agy dispatch plus a new discussion; recovery required keystroke injection from another session. +- **ISSUE CITATIONS ALWAYS CARRY REPO + NUMBER + TITLE**: Every mention of a GitHub issue or PR — in chat, in a commit message, in an issue/PR body, in a memory or queue file — must be written as `repo#number "title"`, e.g. `web-jam-back#998 "email subject or title still not easy for me to see its target venue"`. **`#` followed by digits is an ILLEGAL token in anything Josh reads.** There is no exception for a repeat mention, a list item, a parenthetical, "the one I just named", or a closing one-line offer. If you don't know the title, look it up (`gh issue view N --repo R --json title`) before writing the sentence — never emit a bare number as a placeholder. If the full citation is too verbose, shorten to the TITLE, never to the number. The violation is almost always the LAST sentence of a message (the "want me to do X?" offer, written after the careful part), so re-read the finished message and check every `#` before sending. Josh has asked for this five times (2026-07-24 → 2026-07-29); he reads these on a phone with many numbers in flight and a bare number costs him a lookup every time. +- **NO AGENT CONNECTS A NEW ACCOUNT, CREDENTIAL, OR MCP SERVER WITHOUT AUTHORIZATION:** No agent adds a connector, account, credential, or MCP server to any Claude or Flash surface without Josh's explicit authorization naming it. Discovering that something *could* be connected is never permission to connect it. This applies to new OAuth grants, new MCP servers, new API tokens, and widening the scope of an existing connection. Origin (2026-07-30, Josh): *"it should NEVER have something else that I have not authorized."* See web-jam-tools#324 "No agent connects a new account, credential, or MCP server without Josh's explicit authorization — add the rule and audit where it can be mechanically enforced" for the enforcement-surface audit. +- **STANDING AGENT CREDENTIAL CLASSIFICATION RULE (MACHINE-CONSUMED VS HUMAN-CONSUMED):** Whenever an agent encounters or generates a new credential, account identifier, or token, the agent must **STOP and prompt Josh to classify it** as either machine-consumed (e.g. `GITHUB_TOKEN`, `GEMINI_API_KEY`, `HEROKU_API_KEY`, `CIRCLECI_TOKEN`, `DENO_DEPLOY_TOKEN` stored in shell rc or secret store) or human-consumed (e.g. `webjam.claude@gmail.com` stored in KeePass only) BEFORE storing, exporting, or configuring it in any shell profile, `.env` file, or configuration file. Human-consumed credentials belong in KeePass only and must never be exported to shell profiles or stored in application configuration files (web-jam-tools#344 "Human-only credentials register and guard hook"). +- **NO AI DELETES OR FORCE-PUSHES A REMOTE BRANCH, EVER, WITHOUT AN EXPLICIT IMPERATIVE FROM JOSH NAMING THAT BRANCH.** "The PR is merged" is NOT such an instruction — it states a fact, it does not authorize deleting anything. Local branch cleanup after a merge (deleting a LOCAL branch with `git branch -d`/`-D`, `git fetch --prune` to prune stale local remote-tracking refs) remains permitted and unchanged — this rule narrows that standing post-merge cleanup habit to local branches only, it does not remove it or require re-approval for it. Enforced by three independent layers: a harness `permissions.deny` block on the ways `git push`/`git branch` can delete or clobber a remote ref (`--delete`/`-d`, empty-source colon refspecs, `--force`/`-f`/`--force-with-lease`, `--mirror`, `--prune`, and `git branch -D`/`--delete --force` against a `remotes/` ref — installed via `scripts/install-hooks.sh` in this repo), a GitHub ruleset restricting deletions on the branches agents create (`claude/**`, `agy/**`, `dev`, `main` — Josh-only UI work, see web-jam-tools#308 "Remote branches can be deleted by an agent with no authorization — advisory guard does not block (3 layers: deny rules, GitHub ruleset, HARD RULES)"), and this HARD RULE. Origin: 2026-07-29, an agent deleted `claude/cross-ai-rules-issue-citation-hard-rule` from `web-jam-tools` immediately after Josh merged web-jam-tools#307 "Add ISSUE CITATIONS hard rule to operational rules" — Josh had only said the PR was merged, never authorized a deletion, and the `PreToolUse` guard that fired was advisory text an agent could rationalize past. +- **REAPER RECORDING SESSIONS & RATE LIMIT SAFETY:** When running REAPER music recording sessions via Reaper MCP: + 1. REAPER DAW, audio interfaces, recorded WAV audio stems, and `.RPP` project files live locally on the user's computer and are 100% safe from rate limit interruptions. + 2. Google does NOT broadcast an advance warning gauge prior to hitting temporary hourly rate limits (`429 Rate Limit Exceeded`). + 3. Use **`Flash Med`** for routine, high-volume REAPER operations (`transport_play`, `transport_stop`, `track_create`, volume/pan tweaks, clip splits) to preserve hourly token headroom. + 4. Reserve **`Flash High`** for complex multi-track creative mixing, sidechain routing, and intricate composition passes. + 5. Always execute a project save (`project_save`) before running large multi-step automated sequences. +- **MAIN BRANCH PRs MUST ORIGINATE FROM DEV:** Across all 8 active WebJamApps repos, any PR targeting `main` must originate from `dev` as its head branch (`dev` → `main`). Feature branches (`gemini/*`, `claude/*`, `feat/*`, `fix/*`) must target `dev` as their base branch. Direct PRs from feature branches to `main` are strictly forbidden and blocked by CI and script guardrails (web-jam-tools#351 "all 8 active github repos - their main branch only accepts PR requests from their dev branch"). +- **MULTI-REPO ISSUES STAY OPEN UNTIL ALL REPOS ARE COMPLETE:** When an issue explicitly covers multiple repositories (e.g. "all 8 active github repos"), no single PR in one repository may pass `--closes` or claim the issue is completed. PRs in individual repos must use `--part-of` so the tracking issue remains OPEN until the final repository's PR is merged. +- **THE `Blocked` LABEL IS CANONICAL — NATIVE ISSUE DEPENDENCIES DO NOT REPLACE IT.** Josh wants BOTH: native GitHub issue-dependency links (the real relationship between issues) AND the `Blocked` label (capital B, hex `B60205`, `repos: all` in `skills/fix-labels/labels.yaml`) as the at-a-glance signal that makes an unworkable issue obvious in a plain list view without opening each issue. They do different jobs: use a native dependency whenever a **specific issue** blocks the work — it names which one, renders in the Issues list, and clears itself on close. Use the `Blocked` label whenever the work is unworkable **for any reason**, including the many with no issue to point at (a vendor, a credential Josh must generate, a physical action). Native dependencies cannot express that case at all, which is why the label is not redundant. No agent may prune `Blocked` from `labels.yaml` (or delete it live) on the theory that native dependencies made it redundant — that is exactly what happened once already: `blocked` (lowercase) was removed in commit 7d2523d as part of a nine-label prune shipped for web-jam-tools#300, justified as "-> native issue dependencies," and Josh never actually agreed to that one — it rode along in a batch whose headline was about priority labels. web-jam-tools#329 "Restore the Blocked label as canonical in labels.yaml — it was pruned in a batch Josh never ratified, and he wants it alongside native dependencies" restored it. See `skills/fix-labels/labels.yaml`'s `Blocked` entry for the full rationale. + ## TypeScript & Type Safety - **No `any`**: `@typescript-eslint/no-explicit-any` is set to `'error'`. Do not disable this rule or use `: any` or `as any`. - **Shared Domain Types**: Centralized types for socket connections, streams, payloads, and domain objects live in `src/types/index.ts`. @@ -12,4 +37,3 @@ ## System & Environment - **Runtime:** Node.js v24.18.1 - **Node Engine Version Bumps:** When bumping Node.js in `package.json` `engines.node`, run `npm install --package-lock-only --ignore-scripts` (or `npm install --ignore-scripts`) to update `package-lock.json` root engine definition without waiting on `postinstall` scripts so both files are committed together. - diff --git a/package.json b/package.json index d3def2b..3a23129 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.17", + "version": "3.0.18", "license": "MIT", "type": "module", "main": "build/src/index.js", From f030753e3b046963f365637ca576555e48228b97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:49:52 +0000 Subject: [PATCH 08/15] chore(deps): Bump brace-expansion from 5.0.8 to 5.0.9 Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.8 to 5.0.9. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.8...v5.0.9) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 5.0.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c40fa3..1e8f0ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.17", + "version": "3.0.18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.17", + "version": "3.0.18", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1802,9 +1802,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" From a90c46764e57b8cfa26b69529a7c7fd5eb4b51e3 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Wed, 5 Aug 2026 06:54:15 -0400 Subject: [PATCH 09/15] docs: sync operational hard rules (restricted laptop Dropbox scope) into AGENTS.md per web-jam-tools#321 --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index e4a7814..e7e61c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,7 @@ - **MAIN BRANCH PRs MUST ORIGINATE FROM DEV:** Across all 8 active WebJamApps repos, any PR targeting `main` must originate from `dev` as its head branch (`dev` → `main`). Feature branches (`gemini/*`, `claude/*`, `feat/*`, `fix/*`) must target `dev` as their base branch. Direct PRs from feature branches to `main` are strictly forbidden and blocked by CI and script guardrails (web-jam-tools#351 "all 8 active github repos - their main branch only accepts PR requests from their dev branch"). - **MULTI-REPO ISSUES STAY OPEN UNTIL ALL REPOS ARE COMPLETE:** When an issue explicitly covers multiple repositories (e.g. "all 8 active github repos"), no single PR in one repository may pass `--closes` or claim the issue is completed. PRs in individual repos must use `--part-of` so the tracking issue remains OPEN until the final repository's PR is merged. - **THE `Blocked` LABEL IS CANONICAL — NATIVE ISSUE DEPENDENCIES DO NOT REPLACE IT.** Josh wants BOTH: native GitHub issue-dependency links (the real relationship between issues) AND the `Blocked` label (capital B, hex `B60205`, `repos: all` in `skills/fix-labels/labels.yaml`) as the at-a-glance signal that makes an unworkable issue obvious in a plain list view without opening each issue. They do different jobs: use a native dependency whenever a **specific issue** blocks the work — it names which one, renders in the Issues list, and clears itself on close. Use the `Blocked` label whenever the work is unworkable **for any reason**, including the many with no issue to point at (a vendor, a credential Josh must generate, a physical action). Native dependencies cannot express that case at all, which is why the label is not redundant. No agent may prune `Blocked` from `labels.yaml` (or delete it live) on the theory that native dependencies made it redundant — that is exactly what happened once already: `blocked` (lowercase) was removed in commit 7d2523d as part of a nine-label prune shipped for web-jam-tools#300, justified as "-> native issue dependencies," and Josh never actually agreed to that one — it rode along in a batch whose headline was about priority labels. web-jam-tools#329 "Restore the Blocked label as canonical in labels.yaml — it was pruned in a batch Josh never ratified, and he wants it alongside native dependencies" restored it. See `skills/fix-labels/labels.yaml`'s `Blocked` entry for the full rationale. +- **RESTRICTED LAPTOP DROPBOX SCOPE & SECURITY GUARDRAILS:** Access to `~/Dropbox` on the laptop is restricted to three approved top-level folders: `joshandmariamusic`, `web-jam-llms`, and `mark_henrickson`. All other top-level `~/Dropbox/*` folders — including `Dropbox/WebJamApps` — are explicitly denied in `permissions.deny` via `install-hooks.sh` for file tools (`Read`, `Edit`, `Write`) and Dropbox MCP mutation tools (`delete`, `move`). Note: Deny rules on file tools do not constrain raw Bash commands (which use string-pattern matching for Bash permission rules), serving as an operational guardrail rather than an absolute security boundary (web-jam-tools#321 "Add the laptop Dropbox deny list, verify Flash confinement, and document the restricted scope"). ## TypeScript & Type Safety - **No `any`**: `@typescript-eslint/no-explicit-any` is set to `'error'`. Do not disable this rule or use `: any` or `as any`. From dc12064e513e7bb68faad6a639f352f4165dd168 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Fri, 7 Aug 2026 15:45:09 -0400 Subject: [PATCH 10/15] docs: sync operational hard rules (approval-is-per-gate) into AGENTS.md Per WebJamApps/web-jam-tools#433. --- AGENTS.md | 148 +++++++++++++++++++++++++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 132 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e7e61c6..f08830a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,23 +8,137 @@ - EMAIL: always DRAFT, never send. Save as Gmail draft for Josh's review. - FILES: never create a version-suffixed copy. Edit the master. - Never contact venues, churches, or other third parties directly — Josh handles all outreach. -- **STATE VERIFICATION**: Before any suggestion, to-do item, or "ready for you" claim about a PR/issue/CI/deploy, run a fresh liveness check in that same turn (e.g. `gh pr view --json state,mergedAt` / `gh issue view --json state`). If state ≠ OPEN, it is done: drop it silently. `mergeable: UNKNOWN/null` on a PR usually means merged/closed — never read it as "the API is slow" and never advise merging without confirming state=OPEN. An inconclusive check is not a completed check: use a definitive fallback (local `git merge-tree`, `statusCheckRollup`) or say plainly that you could not verify — never hand Josh a verification step the agent can run itself. -- **ONE REPO, ONE SESSION**: never edit a repo another AI session is actively working (Josh, 2026-07-11). Before branching or editing, check `git status -sb` — a non-`dev` branch or dirty tree means another session likely has the repo in flight. Hand the change to that session/lane (route via Josh) or ask Josh first. A separate worktree or non-colliding branch does NOT make concurrent edits OK — parallel semver bumps and surprise PRs still collide. -- **MAX 2 CONCURRENT WORKSTREAMS PER TERMINAL**: Two live background jobs (e.g. a subagent + a headless agy dispatch) is the cap. When a THIRD thread (new discussion, dispatch, or background job) starts in the same session, the agent must WARN Josh first and propose a separate terminal — never comply silently. Origin: 2026-07-16, Claude A froze mid-permission-prompt while running a Sonnet subagent + a headless agy dispatch plus a new discussion; recovery required keystroke injection from another session. -- **ISSUE CITATIONS ALWAYS CARRY REPO + NUMBER + TITLE**: Every mention of a GitHub issue or PR — in chat, in a commit message, in an issue/PR body, in a memory or queue file — must be written as `repo#number "title"`, e.g. `web-jam-back#998 "email subject or title still not easy for me to see its target venue"`. **`#` followed by digits is an ILLEGAL token in anything Josh reads.** There is no exception for a repeat mention, a list item, a parenthetical, "the one I just named", or a closing one-line offer. If you don't know the title, look it up (`gh issue view N --repo R --json title`) before writing the sentence — never emit a bare number as a placeholder. If the full citation is too verbose, shorten to the TITLE, never to the number. The violation is almost always the LAST sentence of a message (the "want me to do X?" offer, written after the careful part), so re-read the finished message and check every `#` before sending. Josh has asked for this five times (2026-07-24 → 2026-07-29); he reads these on a phone with many numbers in flight and a bare number costs him a lookup every time. -- **NO AGENT CONNECTS A NEW ACCOUNT, CREDENTIAL, OR MCP SERVER WITHOUT AUTHORIZATION:** No agent adds a connector, account, credential, or MCP server to any Claude or Flash surface without Josh's explicit authorization naming it. Discovering that something *could* be connected is never permission to connect it. This applies to new OAuth grants, new MCP servers, new API tokens, and widening the scope of an existing connection. Origin (2026-07-30, Josh): *"it should NEVER have something else that I have not authorized."* See web-jam-tools#324 "No agent connects a new account, credential, or MCP server without Josh's explicit authorization — add the rule and audit where it can be mechanically enforced" for the enforcement-surface audit. -- **STANDING AGENT CREDENTIAL CLASSIFICATION RULE (MACHINE-CONSUMED VS HUMAN-CONSUMED):** Whenever an agent encounters or generates a new credential, account identifier, or token, the agent must **STOP and prompt Josh to classify it** as either machine-consumed (e.g. `GITHUB_TOKEN`, `GEMINI_API_KEY`, `HEROKU_API_KEY`, `CIRCLECI_TOKEN`, `DENO_DEPLOY_TOKEN` stored in shell rc or secret store) or human-consumed (e.g. `webjam.claude@gmail.com` stored in KeePass only) BEFORE storing, exporting, or configuring it in any shell profile, `.env` file, or configuration file. Human-consumed credentials belong in KeePass only and must never be exported to shell profiles or stored in application configuration files (web-jam-tools#344 "Human-only credentials register and guard hook"). -- **NO AI DELETES OR FORCE-PUSHES A REMOTE BRANCH, EVER, WITHOUT AN EXPLICIT IMPERATIVE FROM JOSH NAMING THAT BRANCH.** "The PR is merged" is NOT such an instruction — it states a fact, it does not authorize deleting anything. Local branch cleanup after a merge (deleting a LOCAL branch with `git branch -d`/`-D`, `git fetch --prune` to prune stale local remote-tracking refs) remains permitted and unchanged — this rule narrows that standing post-merge cleanup habit to local branches only, it does not remove it or require re-approval for it. Enforced by three independent layers: a harness `permissions.deny` block on the ways `git push`/`git branch` can delete or clobber a remote ref (`--delete`/`-d`, empty-source colon refspecs, `--force`/`-f`/`--force-with-lease`, `--mirror`, `--prune`, and `git branch -D`/`--delete --force` against a `remotes/` ref — installed via `scripts/install-hooks.sh` in this repo), a GitHub ruleset restricting deletions on the branches agents create (`claude/**`, `agy/**`, `dev`, `main` — Josh-only UI work, see web-jam-tools#308 "Remote branches can be deleted by an agent with no authorization — advisory guard does not block (3 layers: deny rules, GitHub ruleset, HARD RULES)"), and this HARD RULE. Origin: 2026-07-29, an agent deleted `claude/cross-ai-rules-issue-citation-hard-rule` from `web-jam-tools` immediately after Josh merged web-jam-tools#307 "Add ISSUE CITATIONS hard rule to operational rules" — Josh had only said the PR was merged, never authorized a deletion, and the `PreToolUse` guard that fired was advisory text an agent could rationalize past. -- **REAPER RECORDING SESSIONS & RATE LIMIT SAFETY:** When running REAPER music recording sessions via Reaper MCP: - 1. REAPER DAW, audio interfaces, recorded WAV audio stems, and `.RPP` project files live locally on the user's computer and are 100% safe from rate limit interruptions. - 2. Google does NOT broadcast an advance warning gauge prior to hitting temporary hourly rate limits (`429 Rate Limit Exceeded`). - 3. Use **`Flash Med`** for routine, high-volume REAPER operations (`transport_play`, `transport_stop`, `track_create`, volume/pan tweaks, clip splits) to preserve hourly token headroom. - 4. Reserve **`Flash High`** for complex multi-track creative mixing, sidechain routing, and intricate composition passes. - 5. Always execute a project save (`project_save`) before running large multi-step automated sequences. -- **MAIN BRANCH PRs MUST ORIGINATE FROM DEV:** Across all 8 active WebJamApps repos, any PR targeting `main` must originate from `dev` as its head branch (`dev` → `main`). Feature branches (`gemini/*`, `claude/*`, `feat/*`, `fix/*`) must target `dev` as their base branch. Direct PRs from feature branches to `main` are strictly forbidden and blocked by CI and script guardrails (web-jam-tools#351 "all 8 active github repos - their main branch only accepts PR requests from their dev branch"). -- **MULTI-REPO ISSUES STAY OPEN UNTIL ALL REPOS ARE COMPLETE:** When an issue explicitly covers multiple repositories (e.g. "all 8 active github repos"), no single PR in one repository may pass `--closes` or claim the issue is completed. PRs in individual repos must use `--part-of` so the tracking issue remains OPEN until the final repository's PR is merged. -- **THE `Blocked` LABEL IS CANONICAL — NATIVE ISSUE DEPENDENCIES DO NOT REPLACE IT.** Josh wants BOTH: native GitHub issue-dependency links (the real relationship between issues) AND the `Blocked` label (capital B, hex `B60205`, `repos: all` in `skills/fix-labels/labels.yaml`) as the at-a-glance signal that makes an unworkable issue obvious in a plain list view without opening each issue. They do different jobs: use a native dependency whenever a **specific issue** blocks the work — it names which one, renders in the Issues list, and clears itself on close. Use the `Blocked` label whenever the work is unworkable **for any reason**, including the many with no issue to point at (a vendor, a credential Josh must generate, a physical action). Native dependencies cannot express that case at all, which is why the label is not redundant. No agent may prune `Blocked` from `labels.yaml` (or delete it live) on the theory that native dependencies made it redundant — that is exactly what happened once already: `blocked` (lowercase) was removed in commit 7d2523d as part of a nine-label prune shipped for web-jam-tools#300, justified as "-> native issue dependencies," and Josh never actually agreed to that one — it rode along in a batch whose headline was about priority labels. web-jam-tools#329 "Restore the Blocked label as canonical in labels.yaml — it was pruned in a batch Josh never ratified, and he wants it alongside native dependencies" restored it. See `skills/fix-labels/labels.yaml`'s `Blocked` entry for the full rationale. -- **RESTRICTED LAPTOP DROPBOX SCOPE & SECURITY GUARDRAILS:** Access to `~/Dropbox` on the laptop is restricted to three approved top-level folders: `joshandmariamusic`, `web-jam-llms`, and `mark_henrickson`. All other top-level `~/Dropbox/*` folders — including `Dropbox/WebJamApps` — are explicitly denied in `permissions.deny` via `install-hooks.sh` for file tools (`Read`, `Edit`, `Write`) and Dropbox MCP mutation tools (`delete`, `move`). Note: Deny rules on file tools do not constrain raw Bash commands (which use string-pattern matching for Bash permission rules), serving as an operational guardrail rather than an absolute security boundary (web-jam-tools#321 "Add the laptop Dropbox deny list, verify Flash confinement, and document the restricted scope"). +- **STATE VERIFICATION**: Before any suggestion, to-do item, or "ready for you" claim about a + PR/issue/CI/deploy, run a fresh liveness check in that same turn (e.g. + `gh pr view --json state,mergedAt` / `gh issue view --json state`). If state ≠ OPEN, it is done: + drop it silently. `mergeable: UNKNOWN/null` on a PR usually means merged/closed — never read it as + "the API is slow" and never advise merging without confirming state=OPEN. An inconclusive check is + not a completed check: use a definitive fallback (local `git merge-tree`, `statusCheckRollup`) or + say plainly that you could not verify — never hand Josh a verification step the agent can run + itself. +- **ONE REPO, ONE SESSION**: never edit a repo another AI session is actively working (Josh, + 2026-07-11). Before branching or editing, check `git status -sb` — a non-`dev` branch or dirty + tree means another session likely has the repo in flight. Hand the change to that session/lane + (route via Josh) or ask Josh first. A separate worktree or non-colliding branch does NOT make + concurrent edits OK — parallel semver bumps and surprise PRs still collide. +- **MAX 2 CONCURRENT WORKSTREAMS PER TERMINAL**: Two live background jobs (e.g. a subagent + a + headless agy dispatch) is the cap. When a THIRD thread (new discussion, dispatch, or background + job) starts in the same session, the agent must WARN Josh first and propose a separate terminal — + never comply silently. Origin: 2026-07-16, Claude A froze mid-permission-prompt while running a + Sonnet subagent + a headless agy dispatch plus a new discussion; recovery required keystroke + injection from another session. +- **ISSUE CITATIONS ALWAYS CARRY REPO + NUMBER + TITLE**: Every mention of a GitHub issue or PR — in + chat, in a commit message, in an issue/PR body, in a memory or queue file — must be written as + `repo#number "title"`, e.g. + `web-jam-back#998 "email subject or title still not easy for me to see its target venue"`. **`#` + followed by digits is an ILLEGAL token in anything Josh reads.** There is no exception for a + repeat mention, a list item, a parenthetical, "the one I just named", or a closing one-line offer. + If you don't know the title, look it up (`gh issue view N --repo R --json title`) before writing + the sentence — never emit a bare number as a placeholder. If the full citation is too verbose, + shorten to the TITLE, never to the number. The violation is almost always the LAST sentence of a + message (the "want me to do X?" offer, written after the careful part), so re-read the finished + message and check every `#` before sending. Josh has asked for this five times (2026-07-24 → + 2026-07-29); he reads these on a phone with many numbers in flight and a bare number costs him a + lookup every time. +- **NO AGENT CONNECTS A NEW ACCOUNT, CREDENTIAL, OR MCP SERVER WITHOUT AUTHORIZATION:** No agent + adds a connector, account, credential, or MCP server to any Claude or Flash surface without Josh's + explicit authorization naming it. Discovering that something _could_ be connected is never + permission to connect it. This applies to new OAuth grants, new MCP servers, new API tokens, and + widening the scope of an existing connection. Origin (2026-07-30, Josh): _"it should NEVER have + something else that I have not authorized."_ See web-jam-tools#324 "No agent connects a new + account, credential, or MCP server without Josh's explicit authorization — add the rule and audit + where it can be mechanically enforced" for the enforcement-surface audit. +- **STANDING AGENT CREDENTIAL CLASSIFICATION RULE (MACHINE-CONSUMED VS HUMAN-CONSUMED):** Whenever + an agent encounters or generates a new credential, account identifier, or token, the agent must + **STOP and prompt Josh to classify it** as either machine-consumed (e.g. `GITHUB_TOKEN`, + `GEMINI_API_KEY`, `HEROKU_API_KEY`, `CIRCLECI_TOKEN`, `DENO_DEPLOY_TOKEN` stored in shell rc or + secret store) or human-consumed (e.g. `webjam.claude@gmail.com` stored in KeePass only) BEFORE + storing, exporting, or configuring it in any shell profile, `.env` file, or configuration file. + Human-consumed credentials belong in KeePass only and must never be exported to shell profiles or + stored in application configuration files (web-jam-tools#344 "Human-only credentials register and + guard hook"). +- **NO AI DELETES OR FORCE-PUSHES A REMOTE BRANCH, EVER, WITHOUT AN EXPLICIT IMPERATIVE FROM JOSH + NAMING THAT BRANCH.** "The PR is merged" is NOT such an instruction — it states a fact, it does + not authorize deleting anything. Local branch cleanup after a merge (deleting a LOCAL branch with + `git branch -d`/`-D`, `git fetch --prune` to prune stale local remote-tracking refs) remains + permitted and unchanged — this rule narrows that standing post-merge cleanup habit to local + branches only, it does not remove it or require re-approval for it. Enforced by three independent + layers: a harness `permissions.deny` block on the ways `git push`/`git branch` can delete or + clobber a remote ref (`--delete`/`-d`, empty-source colon refspecs, + `--force`/`-f`/`--force-with-lease`, `--mirror`, `--prune`, and `git branch -D`/`--delete --force` + against a `remotes/` ref — installed via `scripts/install-hooks.sh` in this repo), a GitHub + ruleset restricting deletions on the branches agents create (`claude/**`, `agy/**`, `dev`, `main` + — Josh-only UI work, see web-jam-tools#308 "Remote branches can be deleted by an agent with no + authorization — advisory guard does not block (3 layers: deny rules, GitHub ruleset, HARD + RULES)"), and this HARD RULE. Origin: 2026-07-29, an agent deleted + `claude/cross-ai-rules-issue-citation-hard-rule` from `web-jam-tools` immediately after Josh + merged web-jam-tools#307 "Add ISSUE CITATIONS hard rule to operational rules" — Josh had only said + the PR was merged, never authorized a deletion, and the `PreToolUse` guard that fired was advisory + text an agent could rationalize past. +- **REAPER RECORDING SESSIONS & RATE LIMIT SAFETY:** When running REAPER music recording sessions + via Reaper MCP: + 1. REAPER DAW, audio interfaces, recorded WAV audio stems, and `.RPP` project files live locally + on the user's computer and are 100% safe from rate limit interruptions. + 2. Google does NOT broadcast an advance warning gauge prior to hitting temporary hourly rate + limits (`429 Rate Limit Exceeded`). + 3. Use **`Flash Med`** for routine, high-volume REAPER operations (`transport_play`, + `transport_stop`, `track_create`, volume/pan tweaks, clip splits) to preserve hourly token + headroom. + 4. Reserve **`Flash High`** for complex multi-track creative mixing, sidechain routing, and + intricate composition passes. + 5. Always execute a project save (`project_save`) before running large multi-step automated + sequences. +- **MAIN BRANCH PRs MUST ORIGINATE FROM DEV:** Across all 8 active WebJamApps repos, any PR + targeting `main` must originate from `dev` as its head branch (`dev` → `main`). Feature branches + (`gemini/*`, `claude/*`, `feat/*`, `fix/*`) must target `dev` as their base branch. Direct PRs + from feature branches to `main` are strictly forbidden and blocked by CI and script guardrails + (web-jam-tools#351 "all 8 active github repos - their main branch only accepts PR requests from + their dev branch"). +- **MULTI-REPO ISSUES STAY OPEN UNTIL ALL REPOS ARE COMPLETE:** When an issue explicitly covers + multiple repositories (e.g. "all 8 active github repos"), no single PR in one repository may pass + `--closes` or claim the issue is completed. PRs in individual repos must use `--part-of` so the + tracking issue remains OPEN until the final repository's PR is merged. +- **THE `Blocked` LABEL IS CANONICAL — NATIVE ISSUE DEPENDENCIES DO NOT REPLACE IT.** Josh wants + BOTH: native GitHub issue-dependency links (the real relationship between issues) AND the + `Blocked` label (capital B, hex `B60205`, `repos: all` in `skills/fix-labels/labels.yaml`) as the + at-a-glance signal that makes an unworkable issue obvious in a plain list view without opening + each issue. They do different jobs: use a native dependency whenever a **specific issue** blocks + the work — it names which one, renders in the Issues list, and clears itself on close. Use the + `Blocked` label whenever the work is unworkable **for any reason**, including the many with no + issue to point at (a vendor, a credential Josh must generate, a physical action). Native + dependencies cannot express that case at all, which is why the label is not redundant. No agent + may prune `Blocked` from `labels.yaml` (or delete it live) on the theory that native dependencies + made it redundant — that is exactly what happened once already: `blocked` (lowercase) was removed + in commit 7d2523d as part of a nine-label prune shipped for web-jam-tools#300, justified as "-> + native issue dependencies," and Josh never actually agreed to that one — it rode along in a batch + whose headline was about priority labels. web-jam-tools#329 "Restore the Blocked label as + canonical in labels.yaml — it was pruned in a batch Josh never ratified, and he wants it alongside + native dependencies" restored it. See `skills/fix-labels/labels.yaml`'s `Blocked` entry for the + full rationale. +- **RESTRICTED LAPTOP DROPBOX SCOPE & SECURITY GUARDRAILS:** Access to `~/Dropbox` on the laptop is + restricted to three approved top-level folders: `joshandmariamusic`, `web-jam-llms`, and + `mark_henrickson`. All other top-level `~/Dropbox/*` folders — including `Dropbox/WebJamApps` — + are explicitly denied in `permissions.deny` via `install-hooks.sh` for file tools (`Read`, `Edit`, + `Write`) and Dropbox MCP mutation tools (`delete`, `move`). Note: Deny rules on file tools do not + constrain raw Bash commands (which use string-pattern matching for Bash permission rules), serving + as an operational guardrail rather than an absolute security boundary (web-jam-tools#321 "Add the + laptop Dropbox deny list, verify Flash confinement, and document the restricted scope"). +- **APPROVAL IS PER GATE.** Approval of a design is not approval to file the tracking issue. + Approval of an issue is not approval to dispatch. Each gate needs its own imperative from Josh + naming that step. An agent writes the issue body to a file (or shows it in chat) and waits; the + `gh issue create` call (or MCP `issue_write` create) follows only the words "file it" (or + equivalent). A dispatch (spawning a subagent, an agy/Flash handoff) follows only an explicit + instruction to dispatch. A single "go" is ambiguous across gates and must never be read as + covering more than one — the expensive, hard-to-reverse half (issue noise, spawned tokens) is + always the later gate, so collapsing gates fails in the direction that costs the most. Origin: + 2026-08-07, during web-jam-tools#426 "/handle-gmails: add recognizers that propose the follow-up + work an email implies, plus a per-session PR that teaches the skill what it learned" design, an + agent treated Josh's single approval of a three-item plan as covering the design, the issue + filing, AND the dispatch — announcing "filing the tracking issue, then dispatching to Sonnet" + before either gate had its own go-ahead. Josh stopped it at the draft stage. See web-jam-tools#433 + "gate issue creation and dispatch mechanically, and write the approval-is-per-gate rule" for the + mechanical half of this fix (ask-rules on `gh issue create` and MCP `issue_write` create, + installed via `scripts/install-hooks.sh`). ## TypeScript & Type Safety - **No `any`**: `@typescript-eslint/no-explicit-any` is set to `'error'`. Do not disable this rule or use `: any` or `as any`. diff --git a/package.json b/package.json index 3a23129..aab8313 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.18", + "version": "3.0.19", "license": "MIT", "type": "module", "main": "build/src/index.js", From ae727be898a9fecc8ae8505712250381c59fb193 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Thu, 13 Aug 2026 04:01:48 -0400 Subject: [PATCH 11/15] Replace cross-AI rules block with pointer to web-jam-tools Part of WebJamApps/web-jam-tools#505. Replaces the marker-delimited 189-line rules block in AGENTS.md with the single-source pointer to docs/cross-ai-rules.md in web-jam-tools, per the design doc at Token_Savings/agents-md-duplication-design-2026-08-12.md. Co-Authored-By: Claude Code --- AGENTS.md | 147 +++------------------------------------------- package-lock.json | 4 +- package.json | 2 +- 3 files changed, 12 insertions(+), 141 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f08830a..14ff02b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,145 +1,16 @@ # Repository Guidelines for WebJamSocketCluster - -## OPERATIONAL HARD RULES (apply to any AI taking action on Josh's behalf) +## Cross-AI hard rules + +The cross-AI hard rules that bind every agent on every surface are NOT duplicated here. They live +in exactly one file: `docs/cross-ai-rules.md` in the **`web-jam-tools` repository**, which normally +sits alongside this repository — `../web-jam-tools/docs/cross-ai-rules.md`, and on Josh's laptop +`/home/joshua/WebJamApps/web-jam-tools/docs/cross-ai-rules.md`. + +Read that file before acting. If you cannot find it, STOP and say so — do not proceed without the +rules and do not reconstruct them from memory or from this file. -- CALENDAR CONFLICT: never schedule over an existing event without Josh's explicit override. -- EMAIL: always DRAFT, never send. Save as Gmail draft for Josh's review. -- FILES: never create a version-suffixed copy. Edit the master. -- Never contact venues, churches, or other third parties directly — Josh handles all outreach. -- **STATE VERIFICATION**: Before any suggestion, to-do item, or "ready for you" claim about a - PR/issue/CI/deploy, run a fresh liveness check in that same turn (e.g. - `gh pr view --json state,mergedAt` / `gh issue view --json state`). If state ≠ OPEN, it is done: - drop it silently. `mergeable: UNKNOWN/null` on a PR usually means merged/closed — never read it as - "the API is slow" and never advise merging without confirming state=OPEN. An inconclusive check is - not a completed check: use a definitive fallback (local `git merge-tree`, `statusCheckRollup`) or - say plainly that you could not verify — never hand Josh a verification step the agent can run - itself. -- **ONE REPO, ONE SESSION**: never edit a repo another AI session is actively working (Josh, - 2026-07-11). Before branching or editing, check `git status -sb` — a non-`dev` branch or dirty - tree means another session likely has the repo in flight. Hand the change to that session/lane - (route via Josh) or ask Josh first. A separate worktree or non-colliding branch does NOT make - concurrent edits OK — parallel semver bumps and surprise PRs still collide. -- **MAX 2 CONCURRENT WORKSTREAMS PER TERMINAL**: Two live background jobs (e.g. a subagent + a - headless agy dispatch) is the cap. When a THIRD thread (new discussion, dispatch, or background - job) starts in the same session, the agent must WARN Josh first and propose a separate terminal — - never comply silently. Origin: 2026-07-16, Claude A froze mid-permission-prompt while running a - Sonnet subagent + a headless agy dispatch plus a new discussion; recovery required keystroke - injection from another session. -- **ISSUE CITATIONS ALWAYS CARRY REPO + NUMBER + TITLE**: Every mention of a GitHub issue or PR — in - chat, in a commit message, in an issue/PR body, in a memory or queue file — must be written as - `repo#number "title"`, e.g. - `web-jam-back#998 "email subject or title still not easy for me to see its target venue"`. **`#` - followed by digits is an ILLEGAL token in anything Josh reads.** There is no exception for a - repeat mention, a list item, a parenthetical, "the one I just named", or a closing one-line offer. - If you don't know the title, look it up (`gh issue view N --repo R --json title`) before writing - the sentence — never emit a bare number as a placeholder. If the full citation is too verbose, - shorten to the TITLE, never to the number. The violation is almost always the LAST sentence of a - message (the "want me to do X?" offer, written after the careful part), so re-read the finished - message and check every `#` before sending. Josh has asked for this five times (2026-07-24 → - 2026-07-29); he reads these on a phone with many numbers in flight and a bare number costs him a - lookup every time. -- **NO AGENT CONNECTS A NEW ACCOUNT, CREDENTIAL, OR MCP SERVER WITHOUT AUTHORIZATION:** No agent - adds a connector, account, credential, or MCP server to any Claude or Flash surface without Josh's - explicit authorization naming it. Discovering that something _could_ be connected is never - permission to connect it. This applies to new OAuth grants, new MCP servers, new API tokens, and - widening the scope of an existing connection. Origin (2026-07-30, Josh): _"it should NEVER have - something else that I have not authorized."_ See web-jam-tools#324 "No agent connects a new - account, credential, or MCP server without Josh's explicit authorization — add the rule and audit - where it can be mechanically enforced" for the enforcement-surface audit. -- **STANDING AGENT CREDENTIAL CLASSIFICATION RULE (MACHINE-CONSUMED VS HUMAN-CONSUMED):** Whenever - an agent encounters or generates a new credential, account identifier, or token, the agent must - **STOP and prompt Josh to classify it** as either machine-consumed (e.g. `GITHUB_TOKEN`, - `GEMINI_API_KEY`, `HEROKU_API_KEY`, `CIRCLECI_TOKEN`, `DENO_DEPLOY_TOKEN` stored in shell rc or - secret store) or human-consumed (e.g. `webjam.claude@gmail.com` stored in KeePass only) BEFORE - storing, exporting, or configuring it in any shell profile, `.env` file, or configuration file. - Human-consumed credentials belong in KeePass only and must never be exported to shell profiles or - stored in application configuration files (web-jam-tools#344 "Human-only credentials register and - guard hook"). -- **NO AI DELETES OR FORCE-PUSHES A REMOTE BRANCH, EVER, WITHOUT AN EXPLICIT IMPERATIVE FROM JOSH - NAMING THAT BRANCH.** "The PR is merged" is NOT such an instruction — it states a fact, it does - not authorize deleting anything. Local branch cleanup after a merge (deleting a LOCAL branch with - `git branch -d`/`-D`, `git fetch --prune` to prune stale local remote-tracking refs) remains - permitted and unchanged — this rule narrows that standing post-merge cleanup habit to local - branches only, it does not remove it or require re-approval for it. Enforced by three independent - layers: a harness `permissions.deny` block on the ways `git push`/`git branch` can delete or - clobber a remote ref (`--delete`/`-d`, empty-source colon refspecs, - `--force`/`-f`/`--force-with-lease`, `--mirror`, `--prune`, and `git branch -D`/`--delete --force` - against a `remotes/` ref — installed via `scripts/install-hooks.sh` in this repo), a GitHub - ruleset restricting deletions on the branches agents create (`claude/**`, `agy/**`, `dev`, `main` - — Josh-only UI work, see web-jam-tools#308 "Remote branches can be deleted by an agent with no - authorization — advisory guard does not block (3 layers: deny rules, GitHub ruleset, HARD - RULES)"), and this HARD RULE. Origin: 2026-07-29, an agent deleted - `claude/cross-ai-rules-issue-citation-hard-rule` from `web-jam-tools` immediately after Josh - merged web-jam-tools#307 "Add ISSUE CITATIONS hard rule to operational rules" — Josh had only said - the PR was merged, never authorized a deletion, and the `PreToolUse` guard that fired was advisory - text an agent could rationalize past. -- **REAPER RECORDING SESSIONS & RATE LIMIT SAFETY:** When running REAPER music recording sessions - via Reaper MCP: - 1. REAPER DAW, audio interfaces, recorded WAV audio stems, and `.RPP` project files live locally - on the user's computer and are 100% safe from rate limit interruptions. - 2. Google does NOT broadcast an advance warning gauge prior to hitting temporary hourly rate - limits (`429 Rate Limit Exceeded`). - 3. Use **`Flash Med`** for routine, high-volume REAPER operations (`transport_play`, - `transport_stop`, `track_create`, volume/pan tweaks, clip splits) to preserve hourly token - headroom. - 4. Reserve **`Flash High`** for complex multi-track creative mixing, sidechain routing, and - intricate composition passes. - 5. Always execute a project save (`project_save`) before running large multi-step automated - sequences. -- **MAIN BRANCH PRs MUST ORIGINATE FROM DEV:** Across all 8 active WebJamApps repos, any PR - targeting `main` must originate from `dev` as its head branch (`dev` → `main`). Feature branches - (`gemini/*`, `claude/*`, `feat/*`, `fix/*`) must target `dev` as their base branch. Direct PRs - from feature branches to `main` are strictly forbidden and blocked by CI and script guardrails - (web-jam-tools#351 "all 8 active github repos - their main branch only accepts PR requests from - their dev branch"). -- **MULTI-REPO ISSUES STAY OPEN UNTIL ALL REPOS ARE COMPLETE:** When an issue explicitly covers - multiple repositories (e.g. "all 8 active github repos"), no single PR in one repository may pass - `--closes` or claim the issue is completed. PRs in individual repos must use `--part-of` so the - tracking issue remains OPEN until the final repository's PR is merged. -- **THE `Blocked` LABEL IS CANONICAL — NATIVE ISSUE DEPENDENCIES DO NOT REPLACE IT.** Josh wants - BOTH: native GitHub issue-dependency links (the real relationship between issues) AND the - `Blocked` label (capital B, hex `B60205`, `repos: all` in `skills/fix-labels/labels.yaml`) as the - at-a-glance signal that makes an unworkable issue obvious in a plain list view without opening - each issue. They do different jobs: use a native dependency whenever a **specific issue** blocks - the work — it names which one, renders in the Issues list, and clears itself on close. Use the - `Blocked` label whenever the work is unworkable **for any reason**, including the many with no - issue to point at (a vendor, a credential Josh must generate, a physical action). Native - dependencies cannot express that case at all, which is why the label is not redundant. No agent - may prune `Blocked` from `labels.yaml` (or delete it live) on the theory that native dependencies - made it redundant — that is exactly what happened once already: `blocked` (lowercase) was removed - in commit 7d2523d as part of a nine-label prune shipped for web-jam-tools#300, justified as "-> - native issue dependencies," and Josh never actually agreed to that one — it rode along in a batch - whose headline was about priority labels. web-jam-tools#329 "Restore the Blocked label as - canonical in labels.yaml — it was pruned in a batch Josh never ratified, and he wants it alongside - native dependencies" restored it. See `skills/fix-labels/labels.yaml`'s `Blocked` entry for the - full rationale. -- **RESTRICTED LAPTOP DROPBOX SCOPE & SECURITY GUARDRAILS:** Access to `~/Dropbox` on the laptop is - restricted to three approved top-level folders: `joshandmariamusic`, `web-jam-llms`, and - `mark_henrickson`. All other top-level `~/Dropbox/*` folders — including `Dropbox/WebJamApps` — - are explicitly denied in `permissions.deny` via `install-hooks.sh` for file tools (`Read`, `Edit`, - `Write`) and Dropbox MCP mutation tools (`delete`, `move`). Note: Deny rules on file tools do not - constrain raw Bash commands (which use string-pattern matching for Bash permission rules), serving - as an operational guardrail rather than an absolute security boundary (web-jam-tools#321 "Add the - laptop Dropbox deny list, verify Flash confinement, and document the restricted scope"). -- **APPROVAL IS PER GATE.** Approval of a design is not approval to file the tracking issue. - Approval of an issue is not approval to dispatch. Each gate needs its own imperative from Josh - naming that step. An agent writes the issue body to a file (or shows it in chat) and waits; the - `gh issue create` call (or MCP `issue_write` create) follows only the words "file it" (or - equivalent). A dispatch (spawning a subagent, an agy/Flash handoff) follows only an explicit - instruction to dispatch. A single "go" is ambiguous across gates and must never be read as - covering more than one — the expensive, hard-to-reverse half (issue noise, spawned tokens) is - always the later gate, so collapsing gates fails in the direction that costs the most. Origin: - 2026-08-07, during web-jam-tools#426 "/handle-gmails: add recognizers that propose the follow-up - work an email implies, plus a per-session PR that teaches the skill what it learned" design, an - agent treated Josh's single approval of a three-item plan as covering the design, the issue - filing, AND the dispatch — announcing "filing the tracking issue, then dispatching to Sonnet" - before either gate had its own go-ahead. Josh stopped it at the draft stage. See web-jam-tools#433 - "gate issue creation and dispatch mechanically, and write the approval-is-per-gate rule" for the - mechanical half of this fix (ask-rules on `gh issue create` and MCP `issue_write` create, - installed via `scripts/install-hooks.sh`). - ## TypeScript & Type Safety - **No `any`**: `@typescript-eslint/no-explicit-any` is set to `'error'`. Do not disable this rule or use `: any` or `as any`. - **Shared Domain Types**: Centralized types for socket connections, streams, payloads, and domain objects live in `src/types/index.ts`. diff --git a/package-lock.json b/package-lock.json index 1e8f0ed..549d5fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.18", + "version": "3.0.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.18", + "version": "3.0.20", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index aab8313..32714ae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.19", + "version": "3.0.20", "license": "MIT", "type": "module", "main": "build/src/index.js", From 323894b517eeac12767d9ecbff4edac737ca5032 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Sat, 15 Aug 2026 16:54:17 -0400 Subject: [PATCH 12/15] chore: upgrade to Node.js 24.19.0 and cimg/node:24.19.0-browsers (v3.0.21) --- .circleci/config.yml | 4 ++-- .nvmrc | 2 +- AGENTS.md | 2 +- package-lock.json | 6 +++--- package.json | 4 ++-- scripts/smoke-prod-socket.mjs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 575828a..7d63c07 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,8 +2,8 @@ version: 2.1 jobs: build: docker: - # Node 24.18 (engines/.nvmrc) — matching browsers image. - - image: cimg/node:24.18-browsers + # Node 24.19 (engines/.nvmrc) — matching browsers image. + - image: cimg/node:24.19.0-browsers working_directory: ~/repo steps: - checkout diff --git a/.nvmrc b/.nvmrc index 8dfc5cb..60ade1a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.18.1 +24.19.0 diff --git a/AGENTS.md b/AGENTS.md index 14ff02b..d8e76d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,5 +21,5 @@ rules and do not reconstruct them from memory or from this file. - **Snyk Failures & Resolution via `npm audit fix`**: PR checks may report failure on `security/snyk` due to transitive dependency vulnerabilities. Running `npm audit fix` updates `package-lock.json` with non-breaking patches to resolve these vulnerabilities. Always run local tests afterwards to verify the test suite remains 100% green before committing and pushing `package-lock.json` to the PR branch. ## System & Environment -- **Runtime:** Node.js v24.18.1 +- **Runtime:** Node.js v24.19.0 - **Node Engine Version Bumps:** When bumping Node.js in `package.json` `engines.node`, run `npm install --package-lock-only --ignore-scripts` (or `npm install --ignore-scripts`) to update `package-lock.json` root engine definition without waiting on `postinstall` scripts so both files are committed together. diff --git a/package-lock.json b/package-lock.json index 549d5fc..4f85a2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.20", + "version": "3.0.21", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.20", + "version": "3.0.21", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -53,7 +53,7 @@ "vitest": "^4.1.9" }, "engines": { - "node": "24.18.1" + "node": "24.19.0" } }, "node_modules/@babel/helper-string-parser": { diff --git a/package.json b/package.json index 32714ae..891b1af 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.20", + "version": "3.0.21", "license": "MIT", "type": "module", "main": "build/src/index.js", "engines": { - "node": "24.18.1" + "node": "24.19.0" }, "contributors": [ { diff --git a/scripts/smoke-prod-socket.mjs b/scripts/smoke-prod-socket.mjs index ad7d661..c5ff90e 100644 --- a/scripts/smoke-prod-socket.mjs +++ b/scripts/smoke-prod-socket.mjs @@ -26,7 +26,7 @@ // bind address. Dialling by the hostname 'localhost' leaves address // selection to Node's resolver, and that has bitten this exact test once // already: confirmed empirically against the actual CI image -// (cimg/node:24.18-browsers) that Node 24's *default* `dns.lookup` +// (cimg/node:24.19.0-browsers) that Node 24's *default* `dns.lookup` // ('localhost') resolves `::1` first. If a given environment's IPv6 // loopback is present but not actually routable (a known class of container // networking quirk we could not force-reproduce locally, but which matches From 1d77061f283042ae33a833b016e58c5575400db5 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Wed, 26 Aug 2026 01:43:11 -0400 Subject: [PATCH 13/15] feat: configure jscpd, eslint-plugin-unicorn, and eslint-plugin-promise - add .jscpd.json at root with threshold 5 - add jscpd script and wire into test script - add eslint-plugin-unicorn and eslint-plugin-promise to devDependencies - configure unicorn and promise in eslint.config.mjs - bump version to 3.0.22 --- .jscpd.json | 15 + eslint.config.mjs | 23 ++ package-lock.json | 671 +++++++++++++++++++++++++++++++++++++- package.json | 12 +- src/AgController/utils.ts | 2 +- src/app/appUtils.ts | 2 +- 6 files changed, 717 insertions(+), 8 deletions(-) create mode 100644 .jscpd.json diff --git a/.jscpd.json b/.jscpd.json new file mode 100644 index 0000000..53a07c5 --- /dev/null +++ b/.jscpd.json @@ -0,0 +1,15 @@ +{ + "threshold": 5, + "reporters": ["console"], + "absolute": true, + "gitignore": true, + "format": ["typescript", "tsx", "javascript", "jsx"], + "ignore": [ + "**/*.spec.tsx", + "**/*.spec.ts", + "**/*.test.*", + "**/node_modules/**", + "**/build/**", + "**/coverage/**" + ] +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 8cad0c1..8ddbb93 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,6 +3,8 @@ import tseslint from 'typescript-eslint'; import nodePlugin from 'eslint-plugin-n'; import securityPlugin from 'eslint-plugin-security'; import sonarjs from 'eslint-plugin-sonarjs'; +import unicorn from 'eslint-plugin-unicorn'; +import promise from 'eslint-plugin-promise'; import globals from 'globals'; export default tseslint.config( @@ -24,6 +26,25 @@ export default tseslint.config( nodePlugin.configs['flat/recommended-module'], securityPlugin.configs.recommended, sonarjs.configs.recommended, + promise.configs['flat/recommended'], + { + plugins: { + unicorn, + }, + rules: { + 'unicorn/prefer-node-protocol': 'warn', + 'unicorn/no-unreadable-array-destructuring': 'warn', + 'unicorn/no-useless-promise-resolve-reject': 'off', + 'unicorn/prefer-array-find': 'warn', + 'unicorn/prefer-array-flat-map': 'warn', + 'unicorn/prefer-includes': 'warn', + 'unicorn/prefer-string-starts-ends-with': 'warn', + 'unicorn/prefer-date-now': 'warn', + 'unicorn/no-typeof-undefined': 'warn', + 'unicorn/no-invalid-remove-event-listener': 'warn', + 'promise/no-return-wrap': 'off', + }, + }, { files: ['**/*.ts'], extends: [...tseslint.configs.recommendedTypeChecked], @@ -73,6 +94,8 @@ export default tseslint.config( files: ['test/**/*.ts'], rules: { 'sonarjs/no-nested-functions': 'off', + 'promise/always-return': 'off', + 'promise/catch-or-return': 'off', }, }, ); diff --git a/package-lock.json b/package-lock.json index 4f85a2b..ebc8093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.21", + "version": "3.0.22", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.21", + "version": "3.0.22", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -46,9 +46,12 @@ "cross-env": "^10.1.0", "eslint": "^10.2.1", "eslint-plugin-n": "^18.1.0", + "eslint-plugin-promise": "^7.3.0", "eslint-plugin-security": "^4.0.0", "eslint-plugin-sonarjs": "^4.0.3", + "eslint-plugin-unicorn": "^73.0.0", "globals": "^17.5.0", + "jscpd": "^5.0.16", "typescript-eslint": "^8.59.1", "vitest": "^4.1.9" }, @@ -240,6 +243,20 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@eslint/css-tree": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-4.0.5.tgz", + "integrity": "sha512-iPmijIAq4hlIJB86PYmY/fcZORHtjphSqICDbwuw32A/JmkhZQ/K/6TjHE03zqf3n5yABpVcbRAMG8Mi9ojy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.29.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@eslint/js": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", @@ -1729,6 +1746,19 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.19.tgz", + "integrity": "sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -1813,6 +1843,40 @@ "node": "20 || >=22" } }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bson": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/bson/-/bson-7.3.1.tgz", @@ -1903,6 +1967,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1926,6 +2011,29 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", @@ -2038,6 +2146,19 @@ "node": ">= 0.6" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2063,6 +2184,23 @@ "node": ">=6.6.0" } }, + "node_modules/core-js-compat": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -2169,6 +2307,19 @@ "node": ">= 0.8" } }, + "node_modules/detect-indent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.2.tgz", + "integrity": "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2235,6 +2386,13 @@ "async-stream-emitter": "^7.0.1" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.415", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.415.tgz", + "integrity": "sha512-958V+Kbhtgz+SxXeEVKBjrlKRBIDAYvUJfwhjxMZ5S6ut9jAl7l9ZKBkBrvjyjZE36PabLUo2L8kEeV5O4vgJg==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -2265,6 +2423,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2482,6 +2653,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint-plugin-promise": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.3.0.tgz", + "integrity": "sha512-6uGiOR0INuujr6PEQmeSSP7GbIMJ/ebEXXiEzb/nOj68LknH5Pxzb/AbZivmr6VE6TkTE8rTjRK9zhKpK6HsRA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, "node_modules/eslint-plugin-security": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.1.tgz", @@ -2523,6 +2713,44 @@ "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, + "node_modules/eslint-plugin-unicorn": { + "version": "73.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-73.0.0.tgz", + "integrity": "sha512-V0YatLe9nkGhXEXKe2Qljb1EY0sJHwDV0HUF1NKFwtsHh/fU7qGHDgv+6fchzZcgU2/7noHo2gdjnmo0P2uDPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@eslint/css-tree": "^4.0.4", + "browserslist": "^4.28.4", + "change-case": "^5.4.4", + "ci-info": "^4.4.0", + "core-js-compat": "^3.49.0", + "detect-indent": "^7.0.2", + "entities": "^4.5.0", + "find-up-simple": "^1.0.1", + "globals": "^17.7.0", + "indent-string": "^5.0.0", + "is-builtin-module": "^5.0.0", + "is-identifier": "^1.1.0", + "pluralize": "^8.0.0", + "quote-js-string": "^0.1.0", + "regjsparser": "^0.13.2", + "reserved-identifiers": "^1.2.0", + "semver": "^7.8.5", + "strip-indent": "^4.1.1", + "yaml": "^2.9.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=10.4" + } + }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -2850,6 +3078,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -2913,6 +3154,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -3184,6 +3438,22 @@ "url": "https://opencollective.com/express" } }, + "node_modules/identifier-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.1.0.tgz", + "integrity": "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA==", + "dev": true, + "license": "MIT", + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3230,6 +3500,19 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3245,6 +3528,35 @@ "node": ">= 0.10" } }, + "node_modules/is-builtin-module": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-5.0.0.tgz", + "integrity": "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-modules": "^5.0.0" + }, + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-builtin-module/node_modules/builtin-modules": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.3.0.tgz", + "integrity": "sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3268,6 +3580,23 @@ "node": ">=0.10.0" } }, + "node_modules/is-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.1.0.tgz", + "integrity": "sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "identifier-regex": "^1.1.0", + "super-regex": "^1.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3367,6 +3696,133 @@ "dev": true, "license": "MIT" }, + "node_modules/jscpd": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-5.0.16.tgz", + "integrity": "sha512-TiQ4zKtKeldep6UswXFHjVCDhVdLBaJyQcZjhCSzVOmKpT6HBj0jUZiphP1vK1X3VSSuzwcfifJVNpsOIiwRCg==", + "dev": true, + "license": "MIT", + "bin": { + "jscpd": "run-jscpd.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "jscpd-darwin-arm64": "5.0.16", + "jscpd-darwin-x64": "5.0.16", + "jscpd-linux-arm64-gnu": "5.0.16", + "jscpd-linux-x64-gnu": "5.0.16", + "jscpd-linux-x64-musl": "5.0.16", + "jscpd-windows-x64-msvc": "5.0.16" + } + }, + "node_modules/jscpd-darwin-arm64": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/jscpd-darwin-arm64/-/jscpd-darwin-arm64-5.0.16.tgz", + "integrity": "sha512-Tu6OAg3Rp6m7LCZqOtPViJxYGTKXTLrI/xMyjEKe79N9L7GpbI3XhgotGDpoE+PjLRbgJgGhRKbkKK18FPySmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/jscpd-darwin-x64": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/jscpd-darwin-x64/-/jscpd-darwin-x64-5.0.16.tgz", + "integrity": "sha512-3btQ1aG8K7+9rvJjyaRJRcSBD0DvHgZOUVlecjnyO99FYzMvdFREq67UIl5CAXFeYf5EY8TSorCMZQxL6TBD0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/jscpd-linux-arm64-gnu": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/jscpd-linux-arm64-gnu/-/jscpd-linux-arm64-gnu-5.0.16.tgz", + "integrity": "sha512-L1F9CNHxRPqGJCKgGRA4gtZcESACFkS2lgfPgWn9fG+4CkiewLcYyy/oxrafJJL1/PPTQ24sbLuzPaKUxJuvuw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-x64-gnu": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/jscpd-linux-x64-gnu/-/jscpd-linux-x64-gnu-5.0.16.tgz", + "integrity": "sha512-BruP+sAr0+6QsEQZvOkgcS+xR23y4s1hLA0xfkcO1zFFXdnmQdJ4w23/U8VlPwqO2dFRaAEseYEXjHs5oQQGFw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-linux-x64-musl": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/jscpd-linux-x64-musl/-/jscpd-linux-x64-musl-5.0.16.tgz", + "integrity": "sha512-T36RtMnF695Y1/T3cLEhgvspHdKoiF+oxK/lklGJWriLieImZolSOjGXsecW2bp7MNMMGlqLLRzr2RNgnwy+Hg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/jscpd-windows-x64-msvc": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/jscpd-windows-x64-msvc/-/jscpd-windows-x64-msvc-5.0.16.tgz", + "integrity": "sha512-BcrY18r6uje+TjgBKdFZJz6qfAAh9QT24KZYZk+jk6tuIKOYIm6oRMeQEu3Tzr/len9ZethN2h1bKmv+EfHcGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3882,6 +4338,37 @@ "source-map-js": "^1.2.1" } }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -3919,6 +4406,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.29.0.tgz", + "integrity": "sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -4170,6 +4664,16 @@ "node": ">= 0.6" } }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -4253,6 +4757,22 @@ "node": ">= 0.8.0" } }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4285,6 +4805,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4373,6 +4906,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { "version": "8.5.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", @@ -4456,6 +4999,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quote-js-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/quote-js-string/-/quote-js-string-0.1.0.tgz", + "integrity": "sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/quote-js-string?sponsor=1" + } + }, "node_modules/range-parser": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", @@ -4542,6 +5098,32 @@ "regexp-tree": "bin/regexp-tree" } }, + "node_modules/regjsparser": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -5122,6 +5704,37 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-indent": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/super-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", @@ -5158,6 +5771,22 @@ "xtend": "~4.0.1" } }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5361,6 +5990,37 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5586,6 +6246,13 @@ } } }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 891b1af..985adb7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.21", + "version": "3.0.22", "license": "MIT", "type": "module", "main": "build/src/index.js", @@ -34,7 +34,7 @@ "scripts": { "rmrf": "rimraf build && rimraf JaMmusic && rimraf coverage", "dev": "rimraf build && tsc && concurrently --raw \"npm:ts-start\" \"npm:ts-watch\"", - "test": "eslint . && npm run typecheck && rimraf coverage && npm run test:unit", + "test": "eslint . && npm run typecheck && npm run jscpd && rimraf coverage && npm run test:unit", "build:front": "./postinstallJaM.sh && rimraf ./JaMmusic/src && rimraf ./JaMmusic/test", "build:prod": "(cd JaMmusic || exit; npm start -- build)", "cleaninstall": "rimraf yarn.lock && rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm install", @@ -46,7 +46,8 @@ "test:lint": "eslint . --fix", "test:local": "eslint . --fix && npm run test:unit && npm run cc", "test:unit": "vitest run", - "test:prod": "rm -rf node_modules build && npm install --omit=dev && node scripts/smoke-start.mjs" + "test:prod": "rm -rf node_modules build && npm install --omit=dev && node scripts/smoke-start.mjs", + "jscpd": "jscpd src" }, "dependencies": { "@types/debug": "^4.1.7", @@ -89,7 +90,10 @@ "eslint-plugin-sonarjs": "^4.0.3", "globals": "^17.5.0", "typescript-eslint": "^8.59.1", - "vitest": "^4.1.9" + "vitest": "^4.1.9", + "eslint-plugin-promise": "^7.3.0", + "eslint-plugin-unicorn": "^73.0.0", + "jscpd": "^5.0.16" }, "overrides": { "socketcluster-client": { diff --git a/src/AgController/utils.ts b/src/AgController/utils.ts index ffdbc71..31f7109 100644 --- a/src/AgController/utils.ts +++ b/src/AgController/utils.ts @@ -74,7 +74,7 @@ function assertCanCreateGig( } return; } - if (!goodRoles || !user.userType || goodRoles.indexOf(user.userType) === -1) { + if (!goodRoles || !user.userType || !goodRoles.includes(user.userType)) { throw new Error('Not allowed to create new gig'); } } diff --git a/src/app/appUtils.ts b/src/app/appUtils.ts index a158e28..fd58396 100644 --- a/src/app/appUtils.ts +++ b/src/app/appUtils.ts @@ -1,6 +1,6 @@ import Debug from 'debug'; import type { Express, Request, Response } from 'express'; -import type http from 'http'; +import type http from 'node:http'; import type { ISocketConsumer } from '../types/index.js'; interface RequestData { From d8e3bd9af159e6958304d5affe4c9ed051c678d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 01:14:49 +0000 Subject: [PATCH 14/15] chore(deps): Bump qs from 6.15.3 to 6.16.0 Bumps [qs](https://github.com/ljharb/qs) from 6.15.3 to 6.16.0. - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.15.3...v6.16.0) --- updated-dependencies: - dependency-name: qs dependency-version: 6.16.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebc8093..decb417 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4984,9 +4984,9 @@ } }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", From 07556ecc65eaaa04021e10f2d985b49c460a5c9f Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Fri, 4 Sep 2026 16:32:12 -0400 Subject: [PATCH 15/15] fix: widen default-artist gig filter to match jammusic slug - widen getAllByArtistSort default-artist $or query to include jammusic slug - add unit tests asserting jammusic, josh, missing artist, and null matches - assert allGigs and allTours transmission for default artist in AgController - add npm run lint script - bump version to 3.0.23 --- package-lock.json | 4 +- package.json | 3 +- src/model/gig/gig-controller.ts | 14 +++++- test/AgController/index.spec.ts | 6 ++- test/gig/gig-controller.test.ts | 80 ++++++++++++++++++++++++++++++++- 5 files changed, 100 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index decb417..110d351 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.22", + "version": "3.0.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.22", + "version": "3.0.23", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 985adb7..0176797 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.22", + "version": "3.0.23", "license": "MIT", "type": "module", "main": "build/src/index.js", @@ -43,6 +43,7 @@ "ts-start": "cross-env DEBUG=WebJamSocketServer:* node --watch --trace-warnings build/src/index.js", "ts-watch": "tsc -w", "typecheck": "tsc --noEmit", + "lint": "eslint .", "test:lint": "eslint . --fix", "test:local": "eslint . --fix && npm run test:unit && npm run cc", "test:unit": "vitest run", diff --git a/src/model/gig/gig-controller.ts b/src/model/gig/gig-controller.ts index 34c08c7..993a0fe 100644 --- a/src/model/gig/gig-controller.ts +++ b/src/model/gig/gig-controller.ts @@ -6,15 +6,25 @@ import type { IGig, SortOrder } from '../../types/index.js'; // entirely, so treating undefined/null as the default artist keeps the live // JaMmusic site working on both sides of the wj-prod -> web-jam-data Mongo // repoint (before: no artist field at all; after: artist:"josh"). +// Widened (#276) to also match `jammusic` so the live calendar survives the +// web-jam-back#1058 re-tagging migration before DEFAULT_ARTIST is narrowed. export const DEFAULT_ARTIST = 'josh'; class GigController extends Controller { // Scope gigs to an artist. The default artist also matches legacy docs - // that have no `artist` field (or it's null) — see DEFAULT_ARTIST above. + // that have no `artist` field (or it's null) — see DEFAULT_ARTIST above — + // and widened (#276) to match the incoming `jammusic` slug. // Non-default artists (e.g. "tim") match exactly. async getAllByArtistSort(artist: string, sort: SortOrder): Promise { const query = artist === DEFAULT_ARTIST - ? { $or: [{ artist: DEFAULT_ARTIST }, { artist: { $exists: false } }, { artist: null }] } + ? { + $or: [ + { artist: DEFAULT_ARTIST }, + { artist: 'jammusic' }, + { artist: { $exists: false } }, + { artist: null }, + ], + } : { artist }; return this.getAllSort(sort, query); } diff --git a/test/AgController/index.spec.ts b/test/AgController/index.spec.ts index 8604850..5f09ee9 100644 --- a/test/AgController/index.spec.ts +++ b/test/AgController/index.spec.ts @@ -177,16 +177,20 @@ describe('AgControler', () => { }); it('gets all tours', async () => { const agController = new AgController(aStub); + const transmitMock = vi.fn(); const cStub: IClient = { socket: { id: '123', listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }), - transmit: () => { }, + transmit: transmitMock, receiver: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ value: 123, done: true }) }) }), }, }; agController.gigController.getAllByArtistSort = vi.fn(() => Promise.resolve([])); r = await agController.sendGigs(cStub); + expect(agController.gigController.getAllByArtistSort).toHaveBeenCalledWith('josh', { datetime: -1 }); + expect(transmitMock).toHaveBeenCalledWith('allGigs', []); + expect(transmitMock).toHaveBeenCalledWith('allTours', []); expect(r).toBe('sent gigs'); }); it('gets gigs for a non-default artist on a scoped channel', async () => { diff --git a/test/gig/gig-controller.test.ts b/test/gig/gig-controller.test.ts index f3329c3..7aba8d9 100644 --- a/test/gig/gig-controller.test.ts +++ b/test/gig/gig-controller.test.ts @@ -1,5 +1,6 @@ import mongoose from 'mongoose'; -import controller from '../../src/model/gig/gig-controller.js'; +import controller, { DEFAULT_ARTIST } from '../../src/model/gig/gig-controller.js'; +import type { IGig, QueryFilter } from '../../src/types/index.js'; describe('GigController', () => { const testId = new mongoose.Types.ObjectId(); @@ -46,6 +47,83 @@ describe('GigController', () => { controller.model.findByIdAndUpdate = vi.fn(() => Promise.resolve(null)); await expect(controller.findByIdAndUpdate(testId, {})).rejects.toThrow('Id Not Found'); }); + describe('getAllByArtistSort', () => { + const gigJosh = { _id: '1', venue: 'Josh Gig', artist: 'josh' }; + const gigJammusic = { _id: '2', venue: 'JaMmusic Gig', artist: 'jammusic' }; + const gigMissingArtist = { _id: '3', venue: 'Missing Artist Gig' }; + const gigNullArtist = { _id: '4', venue: 'Null Artist Gig', artist: null }; + const gigTim = { _id: '5', venue: 'Tim Gig', artist: 'tim' }; + const allSampleGigs = [gigJosh, gigJammusic, gigMissingArtist, gigNullArtist, gigTim]; + + const matchQuery = (query: Record, doc: Record): boolean => { + if (Array.isArray(query.$or)) { + return query.$or.some((clause: Record) => { + if ('artist' in clause) { + const target = clause.artist; + if (target && typeof target === 'object' && '$exists' in (target as Record)) { + return !('artist' in doc); + } + return doc.artist === target; + } + return false; + }); + } + if ('artist' in query) { + return doc.artist === query.artist; + } + return false; + }; + + beforeEach(() => { + controller.model.findSort = vi.fn((query: QueryFilter) => { + const matched = allSampleGigs.filter((g) => matchQuery(query as Record, g)); + return Promise.resolve(matched as unknown as IGig[]); + }); + }); + + it('builds widened $or query for the default artist including jammusic', async () => { + const findSortMock = vi.fn(() => Promise.resolve([] as IGig[])); + controller.model.findSort = findSortMock; + await controller.getAllByArtistSort(DEFAULT_ARTIST, { datetime: -1 }); + expect(findSortMock).toHaveBeenCalledWith( + { + $or: [ + { artist: DEFAULT_ARTIST }, + { artist: 'jammusic' }, + { artist: { $exists: false } }, + { artist: null }, + ], + }, + { datetime: -1 }, + ); + }); + + it('matches josh, jammusic, missing artist, and null for default artist, and excludes tim', async () => { + const results = await controller.getAllByArtistSort(DEFAULT_ARTIST, { datetime: -1 }); + expect(results).toContain(gigJosh); + expect(results).toContain(gigJammusic); + expect(results).toContain(gigMissingArtist); + expect(results).toContain(gigNullArtist); + expect(results).not.toContain(gigTim); + expect(results).toHaveLength(4); + }); + + it('specifically matches a gig carrying artist: "jammusic" under the default filter', async () => { + const results = await controller.getAllByArtistSort(DEFAULT_ARTIST, { datetime: -1 }); + const jammusicGig = results.find((g) => g.artist === 'jammusic'); + expect(jammusicGig).toBeDefined(); + expect(jammusicGig?.venue).toBe('JaMmusic Gig'); + }); + + it('builds exact match query for non-default artist and returns only matching gigs', async () => { + const results = await controller.getAllByArtistSort('tim', { datetime: -1 }); + expect(results).toEqual([gigTim]); + expect(results).not.toContain(gigJosh); + expect(results).not.toContain(gigJammusic); + expect(results).not.toContain(gigMissingArtist); + expect(results).not.toContain(gigNullArtist); + }); + }); it('should wait unit tests finish before exiting', async () => { const delay = (ms: number) => new Promise((resolve) => { setTimeout(() => resolve(true), ms); }); await delay(1000);