main
111 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b36e0829c6 |
Release v6.3.0: Devin CLI and Hermes Agent support, brainstorming three-path router, SDD/Codex efficiency fixes (#2125)
* fix(codex): suppress SessionStart hook auto-discovery with empty hooks object
Codex auto-discovers a plugin's hooks/hooks.json whenever the Codex
manifest has no `hooks` field: load_plugin_hooks falls back to a
hardcoded DEFAULT_HOOKS_CONFIG_FILE = "hooks/hooks.json" and registers
it. hooks/hooks.json is the Claude Code SessionStart hook, it is tracked
in this repo, and the Codex marketplace installs the whole repo root
(source url "./"), so the fallback re-registered the SessionStart hook
and its install-time trust prompt on Codex.
Removing the Codex hook file and the manifest `hooks` pointer (commit
"Remove Codex hooks") did not disable the hook on Codex — it removed the
explicit declaration that was overriding the fallback, so the fallback
took over and found the Claude hooks/hooks.json.
Declare an empty inline hooks object ({}) in .codex-plugin/plugin.json.
It parses as an empty inline hook set and stops Codex reaching the
auto-discovery fallback. An absent field, an empty array ([]), and an
empty inline list all collapse back to the fallback, so the value must
be exactly {}.
Update the test to assert the manifest declares hooks: {} (and that
hooks/hooks.json exists, which is what makes the declaration necessary),
replacing the prior assertion that the field was absent — which passed
while the hook was still being auto-discovered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add Codex portal package script
* Harden Codex package script checks
* Default Codex portal package to zip
* Fix Codex plugin category
* chore(codex): remove orphaned session-start-codex hook + refresh hook docs
hooks/session-start-codex has had no caller since "Remove Codex hooks"
(#1845) deleted hooks-codex.json and its manifest registration; the
Codex manifest now declares an empty hooks object so Codex registers no
session-start hook at all. The script is Codex-specific dead code —
nothing executes it on Codex or any other harness.
- Delete hooks/session-start-codex.
- tests/hooks/test-session-start.sh: drop the two Codex cases that are
redundant with the generic session-start tests (nested-format and the
legacy-warning omission are already covered by the Claude Code cases).
Re-point the "wrapper dispatches" case to the live `session-start`
script so run-hook.cmd dispatch coverage — used by Claude Code and
Cursor in production — is preserved rather than lost.
- docs/porting-to-a-new-harness.md: Codex is no longer a Shape A
(shell-hook) harness, so re-anchor that worked example to Cursor (a
live shell-hook harness that demonstrates the same per-harness field,
schema, and matcher variance) and mark Codex as native skill discovery
with no session-start hook. Clears the references to the deleted
hooks-codex.json.
- docs/windows/polyglot-hooks.md: the "check hooks-codex.json" pointer
referenced a file deleted in #1845; re-point to hooks-cursor.json.
RELEASE-NOTES.md keeps its historical mention of hooks-codex.json (it
accurately records what that release did). The tests/codex-plugin-sync
fixtures build their own synthetic session-start-codex and test the sync
mechanism generically, so they are intentionally left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: re-anchor Shape A examples away from Codex
* Strip hooks from Codex portal package
* Preserve hooks in Codex package manifest
* Release v6.1.1: fix Codex SessionStart hook re-registration, add Codex portal packaging
* Revert "Remove Gemini CLI support"
This reverts commit
|
||
|
|
c8921b5156 |
fix(systematic-debugging): find-polluter accepts ./-prefixed patterns and matches top-level tests
Follow-up to #2011 (which fixed the ./-prefix mismatch for the documented pattern form): strip a leading ./ from the caller's pattern instead of double-prefixing it into a never-matching ././ form, and also match the pattern with '**/' collapsed, since find -path cannot match '**/' against zero directory levels and silently skipped files directly under the base directory (src/top.test.ts vs src/**/*.test.ts). Adds a deterministic test suite for the script with a stubbed npm. |
||
|
|
0e13ad8222 |
fix(tests): stop the SDD skill test flaking on timing and prose case
tests/claude-code/test-subagent-driven-development.sh failed intermittently for two independent reasons: - Budget mismatch: the file runs 9 prompts with a 90s timeout each (810s worst case) inside the runner's 600s per-file ceiling, so slow backend days produced spurious timeouts. Raise the runner default to 900s and fix the help text, which claimed the default was 300. - Case-sensitive prose matching: the assert helpers grepped free-form model output case-sensitively, but models capitalize the skill's own headings — observed failures include "Do Not Trust the Report" missing pattern "not trust" and a structured answer missing "First:.*spec.*compliance". Match case-insensitively in assert_contains/assert_not_contains/assert_count/assert_order, widen two Test 5 keyword patterns to phrasings observed in real runs, and make assert_order dump the output on failure the way assert_contains already does, so the next flake is diagnosable. Observed 3 failures across 4 runs before the change (timeout, two distinct pattern misses); 3/3 consecutive full runs pass after it. |
||
|
|
5d5b6567a8 |
fix(codex): make package script and its test portable beyond macOS/bsdtar
The packaging pipeline only worked on a Mac with default umask, for
three stacked reasons:
- The deterministic-metadata tar flags (--uid/--gid/--uname/--gname)
are bsdtar spellings; GNU tar rejects them, so the tar.gz archive
step died on Linux. Detect the tar flavor and use --owner=:0
--group=:0 --numeric-owner on GNU tar, which writes byte-identical
ustar headers (uid/gid 0, empty uname/gname).
- Staged file modes depended on two umasks canceling out: git archive
masks entry modes with tar.umask (git default 0002 -> 775), and the
unflagged tar extraction re-masked with the process umask (022 on
macOS -> 755, but 002 elsewhere -> 775). Pin tar.umask=0022 on the
archive call and extract with -p so staged modes are canonical
755/644 on every machine.
- The test's timestamp assertion parsed bsdtar's -tv column layout and
expected epoch 0 rendered in a US timezone ("Dec 31 1969"); GNU tar
uses different columns and UTC hosts render "1970-01-01". Assert
mtime == 0 via python3 tarfile instead, matching how the test
already checks zip timestamps.
tests/codex/test-package-codex-plugin.sh now passes on Linux/GNU tar;
the bsdtar branch preserves the exact flags that passed on macOS.
|
||
|
|
5151e7aebe |
fix(hooks): dispatch the SessionStart hook via Git Bash on Windows
The SessionStart command string starts with a quoted path, which breaks
both Windows shells Claude Code may hand it to: PowerShell parses the
leading quoted string as an expression and dies on the next bareword
('Unexpected token session-start', #1751), and cmd.exe's /c quote rule
drops the outer quotes when the path contains a metacharacter, so a
profile dir like C:\Users\Name(External) truncates the command at the
'(' (#1918). Either way the bootstrap silently never loads.
Declare shell: "bash" on the hook. Claude Code >= 2.1.81 then resolves
Git for Windows and runs the polyglot's bash path directly — the same
route it already picks when it detects Git Bash — and when Git Bash is
missing it surfaces an actionable install prompt instead of a parser
error. Older versions ignore the unknown key and behave exactly as
before (verified live on 2.0.77 and 2.1.80).
Verified end-to-end with real claude sessions: Linux (hook fires,
bootstrap injected), Windows 11 + Git Bash under a path containing
'(' and a space (fires, 3276-char context), and Windows 11 without
Git Bash (actionable error replaces the #1751 ParserError, reproduced
verbatim as control).
Fixes #1751
Fixes #1918
|
||
|
|
6df8ba1458 |
feat(sdd): plan-scoped workspace — one .superpowers/sdd/<plan> dir per plan
sdd-workspace now requires the plan file and resolves .superpowers/sdd/<plan-basename>/; task-brief and review-package write into their plan's directory (review-package gains PLAN_FILE as its first argument). Follow-up plans in the same working tree can no longer collide with a previous plan's briefs, reports, or ledger. |
||
|
|
a60dc2ffe5 |
test(pi): scope mapping assertions to the table, not whole file
The pi tokens (subagent, pi-subagents, Task, TODO.md) also appear in the surrounding prose, so matching the whole file passed even with the mapping table deleted — the exact regression this test exists to catch. Filter to table rows (lines starting with '|') so the assertion fails when the table is gone and passes on dev. Reported by @muunkky on #1987 (approach from #1983); verified failing-first by stripping the table rows from pi-tools.md. |
||
|
|
a80b7b6386 |
test: realign antigravity + pi mapping assertions with pruned references
Commit
|
||
|
|
1c330e1b87 | Preserve hooks in Codex package manifest | ||
|
|
a50def9cef | Strip hooks from Codex portal package | ||
|
|
28b96af905 |
chore(codex): remove orphaned session-start-codex hook + refresh hook docs
hooks/session-start-codex has had no caller since "Remove Codex hooks" (#1845) deleted hooks-codex.json and its manifest registration; the Codex manifest now declares an empty hooks object so Codex registers no session-start hook at all. The script is Codex-specific dead code — nothing executes it on Codex or any other harness. - Delete hooks/session-start-codex. - tests/hooks/test-session-start.sh: drop the two Codex cases that are redundant with the generic session-start tests (nested-format and the legacy-warning omission are already covered by the Claude Code cases). Re-point the "wrapper dispatches" case to the live `session-start` script so run-hook.cmd dispatch coverage — used by Claude Code and Cursor in production — is preserved rather than lost. - docs/porting-to-a-new-harness.md: Codex is no longer a Shape A (shell-hook) harness, so re-anchor that worked example to Cursor (a live shell-hook harness that demonstrates the same per-harness field, schema, and matcher variance) and mark Codex as native skill discovery with no session-start hook. Clears the references to the deleted hooks-codex.json. - docs/windows/polyglot-hooks.md: the "check hooks-codex.json" pointer referenced a file deleted in #1845; re-point to hooks-cursor.json. RELEASE-NOTES.md keeps its historical mention of hooks-codex.json (it accurately records what that release did). The tests/codex-plugin-sync fixtures build their own synthetic session-start-codex and test the sync mechanism generically, so they are intentionally left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cf12786d08 | Default Codex portal package to zip | ||
|
|
1a1243b6dd | Harden Codex package script checks | ||
|
|
da4125d52b | Add Codex portal package script | ||
|
|
7d8d3d4b06 |
fix(codex): suppress SessionStart hook auto-discovery with empty hooks object
Codex auto-discovers a plugin's hooks/hooks.json whenever the Codex
manifest has no `hooks` field: load_plugin_hooks falls back to a
hardcoded DEFAULT_HOOKS_CONFIG_FILE = "hooks/hooks.json" and registers
it. hooks/hooks.json is the Claude Code SessionStart hook, it is tracked
in this repo, and the Codex marketplace installs the whole repo root
(source url "./"), so the fallback re-registered the SessionStart hook
and its install-time trust prompt on Codex.
Removing the Codex hook file and the manifest `hooks` pointer (commit
"Remove Codex hooks") did not disable the hook on Codex — it removed the
explicit declaration that was overriding the fallback, so the fallback
took over and found the Claude hooks/hooks.json.
Declare an empty inline hooks object ({}) in .codex-plugin/plugin.json.
It parses as an empty inline hook set and stops Codex reaching the
auto-discovery fallback. An absent field, an empty array ([]), and an
empty inline list all collapse back to the fallback, so the value must
be exactly {}.
Update the test to assert the manifest declares hooks: {} (and that
hooks/hooks.json exists, which is what makes the declaration necessary),
replacing the prior assertion that the field was absent — which passed
while the hook was still being auto-discovered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
e1753f6e77 |
test(codex): assert Codex manifest ships no hooks
Commit
|
||
|
|
d376057029 |
Keep Codex hooks manifest in plugin metadata
Prompt: Jesse questioned whether the PR should remove the hooks config from the Codex plugin manifest. Runtime investigation showed Codex accepts a committed plugin manifest with hooks and installs the plugin successfully. Removing the field changes behavior: Codex falls back to the default hooks/hooks.json, which uses the non-Codex session-start hook and CLAUDE_PLUGIN_ROOT path, instead of hooks/hooks-codex.json and the session-start-codex script. Changes: restore .codex-plugin/plugin.json hooks to ./hooks/hooks-codex.json and update the Codex marketplace manifest test to require that Codex-specific hook pointer instead of rejecting hooks. Validation: bash tests/codex/test-marketplace-manifest.sh; scripts/lint-shell.sh tests/codex/test-marketplace-manifest.sh; bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh; bash tests/kimi/test-plugin-manifest.sh; bash tests/shell-lint/test-lint-shell.sh. |
||
|
|
add6a283b1 |
Add Codex marketplace manifest
Prompt: Jesse asked for a new worktree off the local superpowers dev branch to add the Codex manifest after diagnosing why github.com/obra/superpowers did not show installable Codex plugins. Root cause: Codex marketplace sources expect a .agents/plugins/marketplace.json at the marketplace root. The superpowers repo only had the Claude marketplace file and the Codex plugin manifest, so Codex could configure the marketplace name but found no installable plugin entries. Changes: add a repo-local Codex marketplace manifest for superpowers-dev that points at this same repository root via the same-root source pattern Codex already accepts; add a focused marketplace manifest test; remove the unsupported hooks field from .codex-plugin/plugin.json so the plugin validator accepts the manifest. Validation: bash tests/codex/test-marketplace-manifest.sh; uv run --with PyYAML python /Users/jesse/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py /Users/jesse/git/superpowers/superpowers/.worktrees/codex-marketplace-manifest; throwaway HOME codex plugin marketplace add/list/add; bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh; bash tests/kimi/test-plugin-manifest.sh; bash tests/shell-lint/test-lint-shell.sh; scripts/lint-shell.sh tests/codex/test-marketplace-manifest.sh. |
||
|
|
549dee6f64 |
test(deps): bump ws to ^8.21.0 in brainstorm-server tests
Clears two dependabot alerts on the test harness's ws dependency: GHSA-96hv-2xvq-fx4p (high, memory-exhaustion DoS, fixed 8.21.0) and GHSA-58qx-3vcg-4xpx (medium, uninitialized memory disclosure, fixed 8.20.1). Test-only — the shipped brainstorm server hand-rolls its WebSocket framing and does not depend on ws. Suite passes (57/57). |
||
|
|
caf14aac66 |
test(sdd): wire test-sdd-workspace.sh into the runner; note git clean -fdx
The per-worktree workspace test was added but never registered in run-skill-tests.sh, so it only ran when invoked by hand. Add it to the fast unit-test array alongside the other pure-shell test. Also document, in the Durable Progress section, that the ledger now lives in git-ignored working-tree scratch, so `git clean -fdx` deletes it — recover from `git log` if that happens. |
||
|
|
667b2c4a2e | test(sdd): lock in per-worktree workspace isolation (#1780) | ||
|
|
93b8444b51 | fix(sdd): write artifacts to working-tree .superpowers/sdd, not .git/ (#1780) | ||
|
|
207a12b203 | feat(sdd): add sdd-workspace helper for a self-ignoring artifact dir | ||
|
|
29c0b1b7db | fix: read Codex plugin version from manifest (PRI-2240) | ||
|
|
cf32920d3a | fix: exclude repo metadata from Codex sync (PRI-1168) | ||
|
|
b3ee712d3a | Add visual companion Prime Radiant branding | ||
|
|
3a907d6a0a | Fix companion stop metadata and token permissions | ||
|
|
1c80914052 | Harden Windows browser launcher | ||
|
|
2a8479b21d | Fix Windows lifecycle validation | ||
|
|
69ed41af9e | Fix companion test cleanup and argv assertions | ||
|
|
51323e4c64 | Harden companion platform tests | ||
|
|
3402d4e7d7 | Fix companion lifecycle test ownership metadata | ||
|
|
6bc49f0183 | Harden companion stop ownership proof | ||
|
|
8f2525a803 | Isolate companion fallback tokens | ||
|
|
85914fbcf8 | Fix server test fallback cleanup | ||
|
|
0410679757 | Harden root screen containment | ||
|
|
69270c9007 | Harden companion Windows lifecycle coverage | ||
|
|
b17d54f839 | Harden brainstorm companion auth regressions | ||
|
|
7fbae0252f |
fix(brainstorm-server): fix auth-integration bugs from full-branch review
A second adversarial review of the merged branch found that combining the session-key auth with the feature work created real bugs the (vacuous) tests missed: - [Critical] GET /files/ (empty name) resolved to CONTENT_DIR and crashed the process with uncaught EISDIR — newly reachable because the query-stripping refactor turns /files/?key=... into /files/. Reject non-regular-file names. - [High] --open opened a KEYLESS url, which the auth gate 403s — the headline feature landed on the error page. Open the keyed url. - [High] Same-port restart regenerated the token (port persisted, token not), so the open tab's old cookie 403'd and never reconnected — contradicting the documented promise. Persist the token (BRAINSTORM_TOKEN_FILE / .last-token) alongside the port. - [Medium] Token sat in world-readable server-info/server.log (0644 in /tmp). umask 077 in start-server.sh + mode 0600 on server-info/.last-token. - [Medium] touchActivity() ran before the auth check, so unauthenticated requests defeated the idle timeout. Count activity only after authorization. - [Low] COOKIE_NAME embedded the pre-fallback port; derive it from the actual bound port (also prevents a cross-server cookie-jar collision on fallback). Tests added/strengthened (previously passed vacuously): /files/ no-crash; the auto-open url carries the key and is reachable (200); restart reuses the same key not just the port; unauthenticated requests don't reset the idle clock. Full suite green (ws-protocol 32, helper 12, auth 13, server 29, lifecycle 8, stop-server 4); restart smoke confirms same port+key and old URL -> 200. |
||
|
|
01de36703d |
test(brainstorm-server): thread session key through tests after auth merge
Integrating the per-session-key auth onto the same branch as the dotfile and lifecycle work: two tests added after the auth commit opened WebSockets without a key (server.test.js dotfile-reload, lifecycle.test.js idle-shutdown), which the auth gate now resets. Pass ?key=/BRAINSTORM_TOKEN in both. Full suite green: ws-protocol 32, helper 12, auth 13, server 28, lifecycle 7, stop-server 4. |
||
|
|
cb5bb885fd |
feat(brainstorm-server): gate every endpoint behind a per-session key
The companion server is reachable by any local browser tab (default loopback bind) and by any host that can route to it (remote --host bind). It served screens, files, and accepted event-injecting WebSocket connections with no authentication, so a malicious browser tab or a direct remote client could read brainstorm content or inject events that the agent reads as the user's input (prompt injection into a live session). Generate a per-session secret token, carry it in the served URL as ?key=, and mirror it into an HttpOnly SameSite=Strict per-port cookie on first load so same-origin subresources and the WebSocket handshake authenticate automatically. Every HTTP request and WebSocket upgrade now requires a valid key (query or cookie, constant-time compared); unauthenticated requests get a friendly 403 explaining they need the full URL. A secret authenticates the client uniformly across loopback, tunnel, and remote binds and defeats DNS rebinding, which a Host/Origin allowlist cannot. Also guard handleMessage against a null JSON payload that crashed the process. Tests: new auth.test.js (13 cases) covering the key on /, /files/*, and WS plus cookie bootstrap and the null-payload guard; server.test.js threads the key; ws-protocol.test.js + auth.test.js wired into npm test. Closes #1014 Refs #1110, #1553, #1504 |
||
|
|
7c805f34d2 |
fix(brainstorm-server): tie stop-server PID check to the session's port
The node+server.cjs command match (from the adversarial review) still matched any unrelated node process running a file named server.cjs. When we recorded the bound port (state/server-info) and lsof is available, additionally require the PID to be the process actually LISTENING on this session's port — which rules out a different project's server.cjs / editor task runner that recycled the stale PID. Falls back to the command match when the port or lsof isn't available. Test: a 'node server.cjs' process not listening on the recorded port is spared. Refs #1703 |
||
|
|
fb08947ded |
fix(brainstorm-server): address adversarial review findings
From a two-reviewer adversarial pass: - [High] EADDRINUSE fallback clobbered the shared .last-port: onListen wrote the bound port unconditionally, so a fallback to a random port overwrote the preferred port another live session still owns — stranding that session's open tab forever. Now persist only when we bound the preferred port (not on fallback). The fallback test now asserts .last-port integrity (teeth-verified). - [Medium] maybeOpenBrowser ran the URL through a shell (exec + JSON.stringify), which does NOT neutralize $(...) in a url-host. Platform launchers now use execFile with the URL as an argv element (no shell). The operator-set BRAINSTORM_OPEN_CMD path stays shell-based (trusted input). - [Medium] --open was a silent no-op on native Windows (no win32 branch). Added. - [Medium] helper.js reconnect/status/tombstone had only substring-grep tests. Added behavioral tests driving the state machine against a mocked browser: Reconnecting+backoff (500->1000->2000), tombstone after the grace period, and reload-on-recovery. - [Low] status pill showed a false 'Connected' before the socket opened; now starts 'Connecting…' until onopen. Not changed (flagged): stop-server.sh's PID-ownership check still matches any 'node ... server.cjs' (narrow residual — a recycled PID onto an unrelated node server.cjs); robust fix needs fragile cross-platform process introspection. |
||
|
|
463dfb7fd4 |
feat(brainstorm-server): opt-in auto-open of the browser on the first screen
When the user approves the visual companion, open their browser automatically the first time a screen is actually ready to show — rather than at startup (just the waiting page) or making them open the URL by hand. Opt-in and gated on approval: off unless BRAINSTORM_OPEN is set (start-server.sh --open, which the agent passes only after the user agrees to use the companion). Even then it fires once, and is skipped if a browser is already connected, on a non-loopback/remote bind, or when headless. Launcher is the platform default (open / xdg-open / WSL cmd.exe) or BRAINSTORM_OPEN_CMD; best-effort, never fatal. lifecycle.test.js: opens once on the first screen when approved; does NOT open without approval. Closes #755 Refs #759 |
||
|
|
dd9fcc21ee |
feat(brainstorm-server): reuse the same port on session restart
When the companion idle-shuts-down and the agent restarts it, a fresh random port meant the user's open browser tab pointed at a dead URL. Persist the bound port per project and prefer it on the next start, so the restarted server comes up on the same port and the open tab's reconnect just works. - start-server.sh exports BRAINSTORM_PORT_FILE=<project>/.superpowers/brainstorm/ .last-port for project sessions (not /tmp). - server.cjs prefers an explicit BRAINSTORM_PORT, else the recorded port, else random; writes the actually-bound port back; and on EADDRINUSE (preferred port still in use) falls back to a random port once instead of crashing. lifecycle.test.js: restart reuses the recorded port; a taken preferred port falls back to a random one without crashing. Refs #1237 |
||
|
|
36ac3e1336 |
feat(brainstorm-companion): resilient reconnect, live status, paused overlay
The injected client reconnected on a fixed 1s timer with no feedback: if the laptop slept or the server restarted, the page showed 'Connected' over a dead socket and silently queued events. And when the server stopped, the user got a bare connection-refused with no explanation. helper.js now: - reconnects with exponential backoff (500ms, doubling, capped at 30s; reset on open), with an onerror->close handler, nulls the socket on close, and clears a pending timer before scheduling another; - drives the frame status pill Connected/Reconnecting/Disconnected via a --status-color custom property (frame-template.html); - after ~15s disconnected, shows a self-styled 'Companion paused' overlay (tombstone) explaining the companion stopped and will reconnect automatically; - on recovery from a tombstoned outage (e.g. server restarted on the same port) reloads to pick up the restarted server's current screen. The reconnect-backoff is an exported pure function; helper.test.js unit-tests it (doubling + cap progression) and asserts the status/tombstone/reconnect wiring. DOM behaviour is verified live. Refs #856, #1237 |
||
|
|
56757f6877 |
feat(brainstorm-server): 4h configurable idle timeout; close WS on shutdown
The companion shut down after only 30 minutes idle — too short for real brainstorming, where a single question can sit far longer. And shutdown() never closed upgraded WebSocket sockets, so an open browser connection could keep the Node process alive after it was supposed to exit. - Default idle timeout raised to 4 hours, configurable via BRAINSTORM_IDLE_TIMEOUT_MS and start-server.sh --idle-timeout-minutes (validated positive integer). - Reported as idle_timeout_ms in the server-started JSON / server-info. - shutdown() now destroys all client sockets so the process exits even with an open WebSocket. - Watchdog check interval is configurable (BRAINSTORM_LIFECYCLE_CHECK_MS, default 60s) so the lifecycle can be tested without minute-long waits. Adds lifecycle.test.js (configured timeout reported; idle shutdown exits despite an open WS — teeth-verified; the start-server flag). Wires ws-protocol, lifecycle, and stop-server suites into npm test. Closes #1237 Refs #1689 |
||
|
|
5ddce063df |
fix(brainstorm-server): verify PID ownership before stopping
stop-server.sh read server.pid and SIGKILL'd that PID with no checks. After a
reboot or PID wraparound the pid file can point at an unrelated, live process —
which we would then kill.
Verify the PID is actually our server (a running 'node ... server.cjs') before
signalling it. If ownership can't be proven, fail closed: remove the stale pid
file and report {status: stale_pid} without killing anything. Real servers still
stop ({status: stopped}); a missing pid file still reports not_running.
Adds stop-server.test.sh covering: an unrelated reused PID is left alone, a real
server is stopped, and a missing pid file.
Refs #1703
|
||
|
|
2b108b7dc2 |
fix(brainstorm-server): ignore macOS resource-fork dotfiles
On macOS (and ExFAT/SMB volumes) the OS writes ._<name>.html sidecar files holding binary resource-fork metadata. These end with .html, so they passed the content filter and could be picked as the newest screen — serving binary garbage to the browser instead of the mockup — or fetched via /files/. Skip dotfiles (leading '.') at all four sites that list or serve content: getNewestScreen, the /files/ endpoint, the known-files seed, and the fs.watch handler. Tests cover serving (/ and /files/) and the watch path (a ._ file must not trigger a reload). Refs #950 |
||
|
|
d9d3d99245 | fix(brainstorming): cap websocket frame payloads |