From b85878a5d5b5979bff29db3161945023887eda5a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 2 Sep 2026 12:51:53 -0700 Subject: [PATCH 1/2] Refactor following-feed scan: extract isFolloweePost to cut CRAP Extract the reply/viewer/membership predicate from scanFollowingFeedTxidsAndCount into an isFolloweePost helper, reducing the scan's cyclomatic complexity from 7 to 5 (CRAP 7.0 to 5.0) while preserving behavior. Refresh the mutation manifest via the approved tool. By refactorer. --- psf-memo-db/src/adapters/post-query.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/psf-memo-db/src/adapters/post-query.js b/psf-memo-db/src/adapters/post-query.js index 803768c..a3d1d2b 100644 --- a/psf-memo-db/src/adapters/post-query.js +++ b/psf-memo-db/src/adapters/post-query.js @@ -56,6 +56,7 @@ class PostQuery { this.topLevelPostTxids = this.topLevelPostTxids.bind(this) this.loadReplyTxids = this.loadReplyTxids.bind(this) this.isReply = this.isReply.bind(this) + this.isFolloweePost = this.isFolloweePost.bind(this) this.scanFollowingFeedTxidsAndCount = this.scanFollowingFeedTxidsAndCount.bind(this) } @@ -320,11 +321,7 @@ class PostQuery { for await (const [key, value] of this.postHeightsDb.iterator({ reverse: true })) { const txid = this.txidFromPostHeight(key, value) - if (replyTxids.has(txid)) continue - - const post = await this.getPostOrNull(txid) - if (!post) continue - if (!followeeSet.has(post.addr)) continue + if (!(await this.isFolloweePost(txid, replyTxids, followeeSet))) continue total++ @@ -340,10 +337,20 @@ class PostQuery { return { txids, total } } + + // True when a post is a top-level (non-reply) post authored by a followed + // address. Loads the post record to check authorship; missing records are + // treated as not matching. + async isFolloweePost (txid, replyTxids, followeeSet) { + if (replyTxids.has(txid)) return false + const post = await this.getPostOrNull(txid) + if (!post) return false + return followeeSet.has(post.addr) + } } export default PostQuery // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-09-02T19:03:53.926Z","module_hash":"19d494a556bfcad4d1f49ed37ab693fea343df3c5e9161702905c852872f08d7","functions":[{"id":"func/PostQuery.constructor","name":"PostQuery.constructor","line":14,"end_line":60,"hash":"1588b3f2716a69f4470536875b0e7a0ef21d34ff6fb9312a2772e71f22d44c2c"},{"id":"func/PostQuery.padHeight","name":"PostQuery.padHeight","line":62,"end_line":64,"hash":"be6c442a4d3d86ab3b60314756b7f7c0592479c21cb3b2e1273dcf139a84fb00"},{"id":"func/PostQuery.postHeightKey","name":"PostQuery.postHeightKey","line":66,"end_line":68,"hash":"2d4dff9464aa4c1e805da5de2ba314fbd856530c046705237ea848d5feff8c7c"},{"id":"func/PostQuery.addrPostHeightKey","name":"PostQuery.addrPostHeightKey","line":70,"end_line":72,"hash":"48579f08593cee36cc2f107c2526ac9def687b354cc5e54089defa3fd37117eb"},{"id":"func/PostQuery.postLikeKey","name":"PostQuery.postLikeKey","line":74,"end_line":76,"hash":"5d16caac2cf88932702b28f9d95927183f8904948eb8c16c7e8c672cd3a0780c"},{"id":"func/PostQuery.txidFromPostHeight","name":"PostQuery.txidFromPostHeight","line":78,"end_line":81,"hash":"0fd135c51089dcc9bd2f166bb9f28faaa6a03d7eb84cf03cb9b36e97cdb3139c"},{"id":"func/PostQuery.txidFromAddrPostHeight","name":"PostQuery.txidFromAddrPostHeight","line":83,"end_line":85,"hash":"314d5432292a78b273e3165343d3e09276600cbaf239f76bcd1b36fe5fcf5e10"},{"id":"func/PostQuery.txidFromKeyParts","name":"PostQuery.txidFromKeyParts","line":88,"end_line":91,"hash":"59fb8173599070095d87e5d6f56eeb999f79e75e4d3f3e435515e9db8ac71868"},{"id":"func/PostQuery.loadReplyTxids","name":"PostQuery.loadReplyTxids","line":93,"end_line":95,"hash":"74621495a3affc6ef8688b9d6a814a91c99b22c86c348d261c952aad25df1661"},{"id":"func/PostQuery.isReply","name":"PostQuery.isReply","line":97,"end_line":105,"hash":"be2e3729bd5f05cbfbab3630678eb5c389bd04c616d53b1e0c48c852f4cb25b1"},{"id":"func/PostQuery.countRepliesForTxids","name":"PostQuery.countRepliesForTxids","line":108,"end_line":122,"hash":"16268cd2a0f8db020a099b704c3d5a3cea5daf6a0936dd1f8d85c38809f006aa"},{"id":"func/PostQuery.buildReplyCountMap","name":"PostQuery.buildReplyCountMap","line":125,"end_line":135,"hash":"1d9762d70dca3439c0bb382b09882faee215f1d53f0656aff8bcbde429ec63b4"},{"id":"func/PostQuery.countLikesForTxids","name":"PostQuery.countLikesForTxids","line":138,"end_line":153,"hash":"54e6e3e6467e72d9dd033c9861b3b9ef5e426a76aed7a13e8ac1a0f0389468a3"},{"id":"func/PostQuery.likeTxidFromPostLike","name":"PostQuery.likeTxidFromPostLike","line":155,"end_line":159,"hash":"7e07a9c278a9abae8f646b4f1950f88760f2d5c6445600a42fa42f36d029a45a"},{"id":"func/PostQuery.buildLikeCountMap","name":"PostQuery.buildLikeCountMap","line":163,"end_line":175,"hash":"a82ca3b46d68426edbe25f176c4a6019ea5fb6abea4c5d5e84aff3b381f63c61"},{"id":"func/PostQuery.postTxidFromPostLike","name":"PostQuery.postTxidFromPostLike","line":177,"end_line":181,"hash":"da7f8d0c63dbc074fb25da1a31dde5075c2c3469b84a5c700bd4a2961879d8e9"},{"id":"func/PostQuery.getPostOrNull","name":"PostQuery.getPostOrNull","line":184,"end_line":191,"hash":"792ce2a8d5f19ed3d159c7af7e95c310e5b0c05cbef4de5be8f8f78403680b91"},{"id":"func/PostQuery.topLevelPostTxids","name":"PostQuery.topLevelPostTxids","line":195,"end_line":203,"hash":"0457d8b43b692d7bbfde283e63663d74542778d3893b45202fcb6ae0f1fc6776"},{"id":"func/PostQuery.scanRecentPostTxids","name":"PostQuery.scanRecentPostTxids","line":205,"end_line":220,"hash":"bed887c0eeec051e082657cac518bbd2f60df84bbb86c9d8aa76015194e7a73a"},{"id":"func/PostQuery.scanPostsByAddrTxidsAndCount","name":"PostQuery.scanPostsByAddrTxidsAndCount","line":226,"end_line":254,"hash":"a872d7a1f71ce1ba6a1dee46a820989cbbb7287dfc433051be1e15890dbc010b"},{"id":"func/PostQuery.scanPostsByAddrTxids","name":"PostQuery.scanPostsByAddrTxids","line":257,"end_line":260,"hash":"3498f2b9615e79b9472a5c7be26520c78db7c1661071c90a882f981d8acca477"},{"id":"func/PostQuery.loadPostsByTxids","name":"PostQuery.loadPostsByTxids","line":262,"end_line":278,"hash":"86b05107f3ecc240b2e734217cedf29ef44c48474bf31c1c8f75f2e4b4f84bea"},{"id":"func/PostQuery.countTopLevelPosts","name":"PostQuery.countTopLevelPosts","line":280,"end_line":291,"hash":"a26a6fdd12201de71965545f4113e3598f325fa4d96076289d371e4157c667ff"},{"id":"func/PostQuery.countTopLevelPostsByAddr","name":"PostQuery.countTopLevelPostsByAddr","line":294,"end_line":309,"hash":"6e12c0b66758cd0103ff6abad1379b3883bea82f084d2382991b4643dfb63360"},{"id":"func/PostQuery.scanFollowingFeedTxidsAndCount","name":"PostQuery.scanFollowingFeedTxidsAndCount","line":314,"end_line":342,"hash":"90920dc6ed0d082545edc8b860f2f6fcc13e632510b9ddf879a4e4c7efa0e652"}]} +// {"version":1,"tested_at":"2026-09-02T19:43:15.045Z","module_hash":"0868137cfaf51799185994217d4ea07263c8a2043ad1f50ed01626e9e3009175","functions":[{"id":"func/PostQuery.constructor","name":"PostQuery.constructor","line":14,"end_line":61,"hash":"ad258234c9efee42e33489f859824240cd39f759a4d9168a4fa42aaa76fe96ec"},{"id":"func/PostQuery.padHeight","name":"PostQuery.padHeight","line":63,"end_line":65,"hash":"be6c442a4d3d86ab3b60314756b7f7c0592479c21cb3b2e1273dcf139a84fb00"},{"id":"func/PostQuery.postHeightKey","name":"PostQuery.postHeightKey","line":67,"end_line":69,"hash":"2d4dff9464aa4c1e805da5de2ba314fbd856530c046705237ea848d5feff8c7c"},{"id":"func/PostQuery.addrPostHeightKey","name":"PostQuery.addrPostHeightKey","line":71,"end_line":73,"hash":"48579f08593cee36cc2f107c2526ac9def687b354cc5e54089defa3fd37117eb"},{"id":"func/PostQuery.postLikeKey","name":"PostQuery.postLikeKey","line":75,"end_line":77,"hash":"5d16caac2cf88932702b28f9d95927183f8904948eb8c16c7e8c672cd3a0780c"},{"id":"func/PostQuery.txidFromPostHeight","name":"PostQuery.txidFromPostHeight","line":79,"end_line":82,"hash":"0fd135c51089dcc9bd2f166bb9f28faaa6a03d7eb84cf03cb9b36e97cdb3139c"},{"id":"func/PostQuery.txidFromAddrPostHeight","name":"PostQuery.txidFromAddrPostHeight","line":84,"end_line":86,"hash":"314d5432292a78b273e3165343d3e09276600cbaf239f76bcd1b36fe5fcf5e10"},{"id":"func/PostQuery.txidFromKeyParts","name":"PostQuery.txidFromKeyParts","line":89,"end_line":92,"hash":"59fb8173599070095d87e5d6f56eeb999f79e75e4d3f3e435515e9db8ac71868"},{"id":"func/PostQuery.loadReplyTxids","name":"PostQuery.loadReplyTxids","line":94,"end_line":96,"hash":"74621495a3affc6ef8688b9d6a814a91c99b22c86c348d261c952aad25df1661"},{"id":"func/PostQuery.isReply","name":"PostQuery.isReply","line":98,"end_line":106,"hash":"be2e3729bd5f05cbfbab3630678eb5c389bd04c616d53b1e0c48c852f4cb25b1"},{"id":"func/PostQuery.countRepliesForTxids","name":"PostQuery.countRepliesForTxids","line":109,"end_line":123,"hash":"16268cd2a0f8db020a099b704c3d5a3cea5daf6a0936dd1f8d85c38809f006aa"},{"id":"func/PostQuery.buildReplyCountMap","name":"PostQuery.buildReplyCountMap","line":126,"end_line":136,"hash":"1d9762d70dca3439c0bb382b09882faee215f1d53f0656aff8bcbde429ec63b4"},{"id":"func/PostQuery.countLikesForTxids","name":"PostQuery.countLikesForTxids","line":139,"end_line":154,"hash":"54e6e3e6467e72d9dd033c9861b3b9ef5e426a76aed7a13e8ac1a0f0389468a3"},{"id":"func/PostQuery.likeTxidFromPostLike","name":"PostQuery.likeTxidFromPostLike","line":156,"end_line":160,"hash":"7e07a9c278a9abae8f646b4f1950f88760f2d5c6445600a42fa42f36d029a45a"},{"id":"func/PostQuery.buildLikeCountMap","name":"PostQuery.buildLikeCountMap","line":164,"end_line":176,"hash":"a82ca3b46d68426edbe25f176c4a6019ea5fb6abea4c5d5e84aff3b381f63c61"},{"id":"func/PostQuery.postTxidFromPostLike","name":"PostQuery.postTxidFromPostLike","line":178,"end_line":182,"hash":"da7f8d0c63dbc074fb25da1a31dde5075c2c3469b84a5c700bd4a2961879d8e9"},{"id":"func/PostQuery.getPostOrNull","name":"PostQuery.getPostOrNull","line":185,"end_line":192,"hash":"792ce2a8d5f19ed3d159c7af7e95c310e5b0c05cbef4de5be8f8f78403680b91"},{"id":"func/PostQuery.topLevelPostTxids","name":"PostQuery.topLevelPostTxids","line":196,"end_line":204,"hash":"0457d8b43b692d7bbfde283e63663d74542778d3893b45202fcb6ae0f1fc6776"},{"id":"func/PostQuery.scanRecentPostTxids","name":"PostQuery.scanRecentPostTxids","line":206,"end_line":221,"hash":"bed887c0eeec051e082657cac518bbd2f60df84bbb86c9d8aa76015194e7a73a"},{"id":"func/PostQuery.scanPostsByAddrTxidsAndCount","name":"PostQuery.scanPostsByAddrTxidsAndCount","line":227,"end_line":255,"hash":"a872d7a1f71ce1ba6a1dee46a820989cbbb7287dfc433051be1e15890dbc010b"},{"id":"func/PostQuery.scanPostsByAddrTxids","name":"PostQuery.scanPostsByAddrTxids","line":258,"end_line":261,"hash":"3498f2b9615e79b9472a5c7be26520c78db7c1661071c90a882f981d8acca477"},{"id":"func/PostQuery.loadPostsByTxids","name":"PostQuery.loadPostsByTxids","line":263,"end_line":279,"hash":"86b05107f3ecc240b2e734217cedf29ef44c48474bf31c1c8f75f2e4b4f84bea"},{"id":"func/PostQuery.countTopLevelPosts","name":"PostQuery.countTopLevelPosts","line":281,"end_line":292,"hash":"a26a6fdd12201de71965545f4113e3598f325fa4d96076289d371e4157c667ff"},{"id":"func/PostQuery.countTopLevelPostsByAddr","name":"PostQuery.countTopLevelPostsByAddr","line":295,"end_line":310,"hash":"6e12c0b66758cd0103ff6abad1379b3883bea82f084d2382991b4643dfb63360"},{"id":"func/PostQuery.scanFollowingFeedTxidsAndCount","name":"PostQuery.scanFollowingFeedTxidsAndCount","line":315,"end_line":339,"hash":"1ef4b7fb555d82c33971d3e0a4d2f630c42eb86261a78cd34765a5e3736e5946"},{"id":"func/PostQuery.isFolloweePost","name":"PostQuery.isFolloweePost","line":344,"end_line":349,"hash":"7825f4509ef5103d2340ecb1434b483b02c7b435a263777ca89e4a0d345f2be3"}]} // mutate4javascript-manifest-end From 5389660b76a708af99bd4cad1369b77f828fec8b Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 2 Sep 2026 13:01:34 -0700 Subject: [PATCH 2/2] Review isFolloweePost refactor: confirm CRAP cut, refresh manifests By architect. --- docs/reviews/following-feed-summary.md | 19 +++++++++++++++++++ psf-memo-client/specs/following-feed.feature | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/reviews/following-feed-summary.md b/docs/reviews/following-feed-summary.md index 174d97d..b6e171b 100644 --- a/docs/reviews/following-feed-summary.md +++ b/docs/reviews/following-feed-summary.md @@ -9,6 +9,25 @@ - `6d1bd14` Implement Following feed (coder) - `d606ee6` Refactor following feed: reduce CRAP/DRY, add property coverage (refactorer) +## Follow-up refactorer handoff (`merge_and_process refactorer b85878a5d5`) +- Merged `swarmforge-refactorer` — commit `b85878a` "Refactor following-feed scan: extract + isFolloweePost to cut CRAP". +- **Architectural review (confirmed good):** `isFolloweePost` is a private helper within + `PostQuery` that extracts the reply/viewer/membership predicate from + `scanFollowingFeedTxidsAndCount`. It adds no new dependencies, preserves information hiding + (the LevelDB model stays encapsulated), and keeps the dependency direction inward. Behavior is + unchanged. +- **CRAP reduction confirmed:** `scanFollowingFeedTxidsAndCount` CC 7→5 (CRAP 7.0→5.0); + `isFolloweePost` CC 3, both 100% covered. +- **Mutation:** `post-query.js` 0 killed / 0 survived / 0 uncovered (manifest refreshed by the + approved tool). +- **DRY:** no new production duplication; only pre-existing test-helper duplicates. +- **Soft Gherkin mutation:** `following-feed.feature` 12 executed, 0 killed, 12 survived — all + genuine equivalents (single-char case mutations of example values), unchanged from the prior + review. +- **Suites:** DB unit 281 passing, property 37 passing, acceptance 9 files passing, lint clean; + client acceptance 19 files passing. + ## Architectural findings and fixes - **UI/Core separation (confirmed good):** Client `FollowingFeedPage` is a thin, testable controller wrapping the `MemoDb` HTTP client; the React `FollowingFeed` component stays a pure diff --git a/psf-memo-client/specs/following-feed.feature b/psf-memo-client/specs/following-feed.feature index 34200c3..70af985 100644 --- a/psf-memo-client/specs/following-feed.feature +++ b/psf-memo-client/specs/following-feed.feature @@ -1,5 +1,5 @@ # acceptance-mutation-manifest-begin -# {"version":1,"tested_at":"2026-09-02T19:13:00.507312989Z","feature_name":"Following Feed","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/following-feed.feature","background_hash":"0d66780cb1b8e277f0ada40a8ffe336dec7a8eaf658f19d2ea344815fb9bf26c","implementation_hash":"unknown","scenarios":[]} +# {"version":1,"tested_at":"2026-09-02T19:55:36.282796191Z","feature_name":"Following Feed","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/following-feed.feature","background_hash":"0d66780cb1b8e277f0ada40a8ffe336dec7a8eaf658f19d2ea344815fb9bf26c","implementation_hash":"unknown","scenarios":[]} # acceptance-mutation-manifest-end # Scenarios: Following Feed - 1, Following Feed - 2, Following Feed - 3, Following Feed - 4, Following Feed - 5, Following Feed - 6, Following Feed - 7