From 8592db3dbcb0db9ed6fbd7d53f6297fa8fe23ee6 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 3 Sep 2026 09:34:51 -0700 Subject: [PATCH 1/4] Add gherkin-parser wrapper for APS Babashka task By coder. --- swarmforge/scripts/gherkin-parser | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 swarmforge/scripts/gherkin-parser diff --git a/swarmforge/scripts/gherkin-parser b/swarmforge/scripts/gherkin-parser new file mode 100755 index 0000000..9e053cd --- /dev/null +++ b/swarmforge/scripts/gherkin-parser @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Wrapper for the Babashka APS gherkin-parser task. +# Delegates to the project-local APS checkout under .swarmforge/tools/aps. +set -euo pipefail +if [ "$#" -ne 2 ]; then + echo "usage: gherkin-parser " >&2 + exit 2 +fi +FEATURE_FILE="$(realpath -m "$1")" +JSON_OUTPUT="$(realpath -m "$2")" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APS_DIR="$(cd "$SCRIPT_DIR/../../.swarmforge/tools/aps" && pwd)" +cd "$APS_DIR" +exec bb gherkin-parser "$FEATURE_FILE" "$JSON_OUTPUT" From d99eda3db46970067ec3499650fa2db648f2b550 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 3 Sep 2026 14:23:38 -0700 Subject: [PATCH 2/4] Add startup bloat check for mutation build dirs Record the tmp/acceptance and target/mutation-workers bloat that made mutate4javascript worker copies many GB and mutation runs appear to hang. Add a startup check that flags any of these dirs over 100MB so they are cleaned before a mutation run. By architect. --- docs/architect-process-notes.md | 13 +++++++++++++ docs/architect-startup.md | 12 ++++++++++++ swarmforge/scripts/architect-startup.sh | 21 +++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/docs/architect-process-notes.md b/docs/architect-process-notes.md index cc8bb15..5e392c9 100644 --- a/docs/architect-process-notes.md +++ b/docs/architect-process-notes.md @@ -49,6 +49,19 @@ distinct from per-task verification results, which live in to keep the worker copies tiny. This cut a notifications-query mutation run from 20+ minutes to ~2 minutes. +- **Which directories bloat and how to keep them clean.** The two directories + that grow without bound are `/tmp/acceptance` (LevelDB dirs from + acceptance runs, up to ~1.5G) and `/target/mutation-workers` + (per-run worker copies, up to ~14G). Both are gitignored build artifacts. + Clean them before any mutation run: + ```bash + rm -rf psf-memo-db/tmp/acceptance psf-memo-db/target/mutation-workers \ + psf-memo-client/tmp/acceptance psf-memo-client/target/mutation-workers + ``` + `architect-startup.sh` now checks these and reports them as `[FAIL]` when + they exceed a size threshold, so a bloated dir is caught before it slows a + mutation run. + - **`memo-db.js` (client HTTP adapter) is excluded from mutation testing.** It uses ESM + a directory import (`../config`) that is only resolvable via react-scripts/webpack, so it cannot be loaded under plain `node --test`. Its diff --git a/docs/architect-startup.md b/docs/architect-startup.md index bc98651..137a04b 100644 --- a/docs/architect-startup.md +++ b/docs/architect-startup.md @@ -48,6 +48,18 @@ Parse survivors with a small python one-liner over `d['results']` (filter `--workers 8` and `--status-interval` so progress is visible. - Run verification sequentially (never concurrent with acceptance generation). +## Keep build dirs clean (mutation speed) +`mutate4javascript` copies the whole project (including `tmp/`) into each worker. +Stale `tmp/acceptance` (LevelDB dirs) and `target/mutation-workers` can bloat to +~1.5G and ~14G, making the worker copy alone many GB and mutation runs appear to +hang. `architect-startup.sh` now flags any of these over 100MB as `[FAIL]`. +Clean them before any mutation run: +```bash +rm -rf psf-memo-db/tmp/acceptance psf-memo-db/target/mutation-workers \ + psf-memo-client/tmp/acceptance psf-memo-client/target/mutation-workers +``` +This cut a notifications-query mutation run from 20+ minutes to ~2 minutes. + ## Refactorer forwards your own review commit back After you send a `priority: 00` review commit to coder+refactorer, the refactorer merges and forwards that SAME commit back to you. It is a no-op ("Already up to diff --git a/swarmforge/scripts/architect-startup.sh b/swarmforge/scripts/architect-startup.sh index 95eea61..e36eab2 100755 --- a/swarmforge/scripts/architect-startup.sh +++ b/swarmforge/scripts/architect-startup.sh @@ -73,6 +73,27 @@ for c in psf-memo-client psf-memo-db psf-memo-indexer; do && ok "$c runner-worker" || bad "$c runner-worker" done +echo "== Bloated build dirs (slow mutation copies) ==" +# tmp/acceptance and target/mutation-workers are gitignored build artifacts +# that mutate4javascript copies into every worker. If they grow large, the +# worker copy alone can be many GB and mutation runs appear to hang. Flag any +# dir over the threshold so it can be cleaned before a mutation run. +BLOAT_KB=102400 # 100 MB +for d in psf-memo-client/tmp/acceptance psf-memo-client/target/mutation-workers \ + psf-memo-db/tmp/acceptance psf-memo-db/target/mutation-workers \ + psf-memo-indexer/tmp/acceptance psf-memo-indexer/target/mutation-workers; do + if [ -d "$d" ]; then + size_kb=$(du -sk "$d" 2>/dev/null | awk '{print $1}') + if [ -n "$size_kb" ] && [ "$size_kb" -gt "$BLOAT_KB" ]; then + bad "$d is ${size_kb}KB (clean with: rm -rf $d)" + else + ok "$d clean" + fi + else + ok "$d absent" + fi +done + echo echo "Result: $pass ok, $fail fail" [ "$fail" -eq 0 ] From edbdbc6019e2583227a5bea0c5bfb298d4870ed5 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 3 Sep 2026 14:28:45 -0700 Subject: [PATCH 3/4] Record architect merge in briefing By specifier. --- specifier-prompt.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specifier-prompt.md b/specifier-prompt.md index cb45f27..f07282c 100644 --- a/specifier-prompt.md +++ b/specifier-prompt.md @@ -359,6 +359,7 @@ At the end of each session, update this file: - Note the current `master` HEAD commit. - State the next feature to work on. -Current `master` HEAD: `d3903fa` (backlog cleared of all previously listed -features; direction set to front-end improvements to `psf-memo-client`). +Current `master` HEAD: `d99eda3` (merged architect's notifications refactor + +startup bloat check; verified db 315 passing + lint clean, client 239 passing + +lint clean + build succeeds). Next action: **ask the user for the next front-end improvement to spec**. From c2946177825429957ac2b35bed8c0f4da6e44ad0 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 3 Sep 2026 16:07:48 -0700 Subject: [PATCH 4/4] Fix profile page wallet address wiring so Follow button appears for other profiles Use the reactive bchWalletState.cashAddress as the viewer address source and include it in the profile load effect dependencies. Extract a small wallet-address selector so the behavior is unit-testable. By coder. --- .../src/components/app-body/profile/index.js | 16 +++-- .../src/services/profile-wallet.js | 19 ++++++ .../test/unit/profile-wallet.test.js | 60 +++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 psf-memo-client/src/services/profile-wallet.js create mode 100644 psf-memo-client/test/unit/profile-wallet.test.js diff --git a/psf-memo-client/src/components/app-body/profile/index.js b/psf-memo-client/src/components/app-body/profile/index.js index 1899759..cab899d 100644 --- a/psf-memo-client/src/components/app-body/profile/index.js +++ b/psf-memo-client/src/components/app-body/profile/index.js @@ -11,6 +11,7 @@ import MemoDb from '../../../services/memo-db' import MemoFollow from '../../../services/memo-follow' import MemoMute from '../../../services/memo-mute' import ProfilePage from '../../../services/profile-page' +import { getViewerAddress } from '../../../services/profile-wallet' import PostReplyCount from '../../post-reply-count' import LikeButton from '../../post-feed/like-button' import PostThreadModal from '../../post-thread-modal' @@ -75,6 +76,10 @@ function Profile (props) { setThreadTxid(null) } + const wallet = appData?.wallet || null + const appProfiles = appData?.profiles || null + const myAddr = getViewerAddress(appData) + const runAction = async (method, failMsg) => { if (!profilePage || busy) return setBusy(true) @@ -98,12 +103,11 @@ function Profile (props) { try { const memoDb = new MemoDb() - const myAddr = appData?.wallet?.walletInfo?.cashAddress || null - const memoFollow = myAddr - ? new MemoFollow({ wallet: appData.wallet, profiles: appData.profiles }) + const memoFollow = myAddr && wallet + ? new MemoFollow({ wallet, profiles: appProfiles }) : null - const memoMute = myAddr - ? new MemoMute({ wallet: appData.wallet, profiles: appData.profiles }) + const memoMute = myAddr && wallet + ? new MemoMute({ wallet, profiles: appProfiles }) : null const page = new ProfilePage({ memoDb, addr, myAddr, memoFollow, memoMute }) @@ -132,7 +136,7 @@ function Profile (props) { setError('Missing profile address') setLoading(false) } - }, [addr, appData?.wallet, appData?.profiles]) + }, [addr, myAddr, wallet, appProfiles]) const showFollowButton = profilePage && profilePage.canFollow() && !profilePage.isFollowing() const showUnfollowButton = profilePage && profilePage.canFollow() && profilePage.isFollowing() diff --git a/psf-memo-client/src/services/profile-wallet.js b/psf-memo-client/src/services/profile-wallet.js new file mode 100644 index 0000000..a54df16 --- /dev/null +++ b/psf-memo-client/src/services/profile-wallet.js @@ -0,0 +1,19 @@ +/* + Wallet address selection for the profile page. + + The React app stores the authenticated wallet address in two places: + `appData.bchWalletState.cashAddress` (reactive state updated as the wallet + initializes) and `appData.wallet.walletInfo.cashAddress` (the wallet object + itself). Prefer the reactive state so the profile page re-loads when the + address becomes available. +*/ + +'use strict' + +function getViewerAddress (appData) { + return appData?.bchWalletState?.cashAddress || + appData?.wallet?.walletInfo?.cashAddress || + null +} + +module.exports = { getViewerAddress } diff --git a/psf-memo-client/test/unit/profile-wallet.test.js b/psf-memo-client/test/unit/profile-wallet.test.js new file mode 100644 index 0000000..2a5b793 --- /dev/null +++ b/psf-memo-client/test/unit/profile-wallet.test.js @@ -0,0 +1,60 @@ +/* + Unit tests for the profile wallet address selector. + + The profile page must show follow/mute controls only when the viewer's + wallet address is known. The React app tracks the loaded address both in the + wallet object and in `bchWalletState`; the selector prefers the reactive + state so the profile reloads when the address becomes available. +*/ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert/strict') +const { getViewerAddress } = require('../../src/services/profile-wallet') + +function makeAppData ({ bchCashAddress, walletCashAddress } = {}) { + return { + bchWalletState: { + cashAddress: bchCashAddress + }, + wallet: walletCashAddress + ? { walletInfo: { cashAddress: walletCashAddress } } + : null, + profiles: {} + } +} + +test('getViewerAddress prefers bchWalletState.cashAddress', () => { + const appData = makeAppData({ + bchCashAddress: 'bitcoincash:state-address', + walletCashAddress: 'bitcoincash:wallet-address' + }) + + assert.equal(getViewerAddress(appData), 'bitcoincash:state-address') +}) + +test('getViewerAddress falls back to wallet.walletInfo.cashAddress', () => { + const appData = makeAppData({ + bchCashAddress: undefined, + walletCashAddress: 'bitcoincash:wallet-address' + }) + + assert.equal(getViewerAddress(appData), 'bitcoincash:wallet-address') +}) + +test('getViewerAddress returns null when no wallet is loaded', () => { + const appData = makeAppData({}) + + assert.equal(getViewerAddress(appData), null) +}) + +test('getViewerAddress returns null when wallet lacks an address', () => { + const appData = { + bchWalletState: {}, + wallet: { walletInfo: {} }, + profiles: {} + } + + assert.equal(getViewerAddress(appData), null) +})